# How to configure Claude Code with a Python type checker


[Claude Code](https://code.claude.com/) does not run a Python type checker by default, and the gap is wider than it looks. Listing `uv run ty check` in `CLAUDE.md` looks like enough configuration, but [Pyrefly's documented agentic-loop pattern](https://pyrefly.org/blog/pyrefly-agentic-loop/) shows that models routinely skip the command unless the type checker is wrapped in a loop that runs after every edit. With the wrapper in place, the model engages with errors; without it, the model treats the checker as optional.

Wiring a type checker into Claude Code takes four layers, each filling a gap the others leave open:

- A `CLAUDE.md` instruction so Claude knows which command to prefer
- A language-server plugin so type errors appear in the conversation as Claude edits
- A `Stop` hook so Claude cannot declare a turn done with unfixed errors
- A [pre-commit](https://pre-commit.com/) gate so the same checks run for every commit, agent or human

This guide walks all four for [ty](https://pydevtools.com/handbook/reference/ty.md), [mypy](https://pydevtools.com/handbook/reference/mypy.md), and [pyright](https://pydevtools.com/handbook/reference/pyright.md). The shape is the same in every case; only the commands and the plugin source change.

## Prerequisites

* [Claude Code](https://code.claude.com/) installed
* [uv](https://pydevtools.com/handbook/reference/uv.md) installed
* A Python project with a type checker added as a dev dependency, for example `uv add --dev ty` or `uv add --dev mypy`
* `jq` if you want shell-based hooks (Python hooks also work)

## Pick the right layer for your goal

Each layer answers a different question. Pick the ones that match what you actually need; nothing requires all four.

| Goal | Layer | ty | mypy | pyright |
|------|-------|----|------|---------|
| Tell Claude which type-checker command to prefer and invoke it through `uv run` | CLAUDE.md | yes | yes | yes |
| Show type errors in the conversation as Claude edits | Language-server plugin | yes (OpenAI) | no first-party plugin | yes (Anthropic) |
| Catch unfixed type errors before a turn ends | `Stop` hook | yes | yes | yes |
| Block a commit that introduces type errors, including from teammates not using Claude | pre-commit | yes (local hook) | yes (mirrors-mypy) | yes (community wrapper) |

The plugin row is the only place the three checkers diverge meaningfully. The other rows work the same way for all three; only the command name changes.

## Point Claude Code at a type checker with CLAUDE.md

A `CLAUDE.md` file in the project root is the simplest place to point Claude at your type checker. Claude reads it on every prompt, so a few lines about the right command keep the model from inventing alternatives like `python -m mypy` or invoking the checker outside the project's virtual environment.

For a ty project, add:

```markdown {filename="CLAUDE.md"}
## Type checking

This project uses ty for type checking. Always invoke through `uv run`.

- Check the whole project: `uv run ty check`
- Check a single path: `uv run ty check src/<path>`
- Configuration lives in `pyproject.toml` under `[tool.ty]`.
```

For mypy, swap `ty check` for `mypy .` (mypy reads `[tool.mypy]` in `pyproject.toml`). For pyright, swap to `uv run pyright` (configured under `[tool.pyright]`).

> [!IMPORTANT]
> Treat this section as guidance, not enforcement. Models read `CLAUDE.md` but routinely skip type checks unless the work is type-shaped, even with explicit instructions. The hook layer below is what makes the checker run on every turn. Keep both: one is documentation, the other is enforcement.

## Install a language-server plugin for live diagnostics

A language-server plugin gives Claude real-time type-check diagnostics as it edits, the same way an IDE surfaces errors to a human developer. Two of the three checkers have a first-party plugin; mypy does not.

### Install OpenAI's plugin for ty

OpenAI publishes an [official Claude Code plugin](https://github.com/astral-sh/claude-code-plugins) that bundles three skills (`/astral:uv`, `/astral:ruff`, `/astral:ty`) and a ty language server. Inside Claude Code, run:

```
/plugin marketplace add astral-sh/claude-code-plugins
/plugin install astral@astral-sh
```

The language server starts automatically on `.py` and `.pyi` files and pipes ty's diagnostics into the conversation. The `/astral:ty` skill loads on demand when you ask Claude to type-check or migrate from mypy or pyright; it does not consume context until invoked. See [How to install OpenAI's Astral plugins for Claude Code](https://pydevtools.com/handbook/how-to/how-to-install-astral-plugins-for-claude-code.md) for the team-wide install via `.claude/settings.json`.

### Install Anthropic's pyright LSP plugin

Anthropic publishes an [official pyright LSP plugin](https://claude.com/plugins/pyright-lsp). It runs Microsoft's pyright as a language server inside Claude Code, providing real-time type checking and error detection. Install with:

```
/plugin install pyright-lsp@claude-plugins-official
```

Once installed, the language server activates on Python files and feeds pyright diagnostics into the conversation. Claude uses them when proposing or evaluating edits.

### Skip the plugin layer for mypy

mypy has no official Claude Code plugin and no maintained third-party wrapper. mypy projects rely on the `CLAUDE.md` instruction plus the `Stop` hook below for the same end-of-turn signal that ty and pyright get during the turn. The trade-off is later feedback: mypy users see errors after the turn ends rather than during edits, which adds round trips when several edits introduce errors in sequence.

## Run a type checker at turn end with a Stop hook

`CLAUDE.md` describes the intent; the `Stop` hook makes it real. A `Stop` hook fires when Claude finishes a turn, and exit code 2 blocks the turn from completing and surfaces the error message back to the model. Without the hook, Claude can declare "done" while type errors remain.

Add this to `.claude/settings.json`:

```json {filename=".claude/settings.json"}
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "uv run ty check >&2 || exit 2",
            "timeout": 30
          }
        ]
      }
    ]
  }
}
```

The `>&2` redirect sends ty's diagnostics to stderr where Claude Code displays them; `exit 2` marks the hook as a blocking failure that the next turn must address. The 30-second timeout keeps a slow check from hanging the session.

For mypy, swap the command to `uv run mypy . >&2 || exit 2`. For pyright, use `uv run pyright >&2 || exit 2`.

> [!CAUTION]
> The hook exits 2 on *any* type error, not only the ones Claude introduced. On a codebase with hundreds of pre-existing errors, every turn ends with a blocking failure and Claude burns turns chasing legacy issues. Scope the command to the path Claude is editing, for example `uv run ty check src/newmodule/`, or configure the checker to ignore legacy modules first. See [How to gradually adopt type checking in an existing Python project](https://pydevtools.com/handbook/how-to/how-to-gradually-adopt-type-checking-in-an-existing-python-project.md) for the path-by-path adoption pattern.

The full hook reference, including `PostToolUse` and `UserPromptSubmit` events, lives in [How to write Claude Code hooks for Python projects](https://pydevtools.com/handbook/how-to/how-to-write-claude-code-hooks-for-python-projects.md).

## Add the type checker to pre-commit

Hooks fire while Claude is in a session; pre-commit fires when anyone runs `git commit`, Claude included. The two are complementary. Pre-commit catches commits where the `Stop` hook is bypassed or misconfigured, and it also gates commits from teammates not using Claude Code at all.

For ty, use a local hook because OpenAI does not yet publish an official pre-commit hook repo ([tracking issue](https://github.com/astral-sh/ty/issues/269)):

```yaml {filename=".pre-commit-config.yaml"}
repos:
  - repo: local
    hooks:
      - id: ty
        name: ty
        entry: uvx ty check
        language: system
        types: [python]
        pass_filenames: false
```

For mypy, use the [mirrors-mypy](https://github.com/pre-commit/mirrors-mypy) repo, pinned to a recent release tag:

```yaml {filename=".pre-commit-config.yaml"}
repos:
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v2.0.0
    hooks:
      - id: mypy
        additional_dependencies: [types-requests]
```

Add any type-stub packages your project needs to `additional_dependencies`. For pyright, the canonical wrapper is [`RobertCraigie/pyright-python`](https://github.com/RobertCraigie/pyright-python):

```yaml {filename=".pre-commit-config.yaml"}
repos:
  - repo: https://github.com/RobertCraigie/pyright-python
    rev: v1.1.409
    hooks:
      - id: pyright
```

Install the hooks once with [prek](https://pydevtools.com/handbook/reference/prek.md) (a faster reimplementation of pre-commit) or pre-commit itself:

```bash
uvx prek install
# or
uvx pre-commit install
```

Now `git commit` runs the type checker before recording the commit. If type errors slip past the `Stop` hook, pre-commit blocks them at the commit boundary. When Claude commits a change, the same hook runs against Claude's edits.

For the full pre-commit configuration including Ruff and other Python tools, see [How to set up pre-commit hooks for a Python project](https://pydevtools.com/handbook/how-to/how-to-set-up-pre-commit-hooks-for-a-python-project.md).

## Combine all four layers

A ty project that uses every layer ends up with three files in the repo. Here is the setup; substitute the commands above to swap in mypy or pyright.

`CLAUDE.md` in the project root:

```markdown {filename="CLAUDE.md"}
## Type checking

This project uses ty for type checking. Always invoke through `uv run`. When working with types or migrating from mypy or pyright, invoke `/astral:ty` to follow OpenAI's recommended usage.

- Check the whole project: `uv run ty check`
- Check a single path: `uv run ty check src/<path>`
- Configuration lives in `pyproject.toml` under `[tool.ty]`.
```

`.claude/settings.json` in the project root:

```json {filename=".claude/settings.json"}
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "uv run ty check >&2 || exit 2",
            "timeout": 30
          }
        ]
      }
    ]
  }
}
```

`.pre-commit-config.yaml` in the project root:

```yaml {filename=".pre-commit-config.yaml"}
repos:
  - repo: local
    hooks:
      - id: ty
        name: ty
        entry: uvx ty check
        language: system
        types: [python]
        pass_filenames: false
```

Commit all three so the configuration travels with the repository.

> [!TIP]
> If your CLAUDE.md is already long, consider the [pydevtools CLAUDE.md template](https://pydevtools.com/handbook/how-to/how-to-use-the-pydevtools-claude-md-template.md). It bundles type checking, linting with [Ruff](https://pydevtools.com/handbook/reference/ruff.md), testing with [pytest](https://pydevtools.com/handbook/reference/pytest.md), and uv invocation rules into one file.

## Learn More

* [How to write Claude Code hooks for Python projects](https://pydevtools.com/handbook/how-to/how-to-write-claude-code-hooks-for-python-projects.md) for the full hooks reference
* [How to install OpenAI's Astral plugins for Claude Code](https://pydevtools.com/handbook/how-to/how-to-install-astral-plugins-for-claude-code.md) for the team-wide ty plugin install
* [How to set up pre-commit hooks for a Python project](https://pydevtools.com/handbook/how-to/how-to-set-up-pre-commit-hooks-for-a-python-project.md) for the full pre-commit setup
* [How to gradually adopt type checking in an existing Python project](https://pydevtools.com/handbook/how-to/how-to-gradually-adopt-type-checking-in-an-existing-python-project.md) before turning the `Stop` hook on
* [How do Python type checkers compare?](https://pydevtools.com/handbook/explanation/how-do-mypy-pyright-and-ty-compare.md) for the trade-offs across mypy, pyright, ty, and Pyrefly
* [ty reference](https://pydevtools.com/handbook/reference/ty.md), [mypy reference](https://pydevtools.com/handbook/reference/mypy.md), [pyright reference](https://pydevtools.com/handbook/reference/pyright.md)
* [Claude Code hooks documentation](https://code.claude.com/docs/en/hooks)
* [OpenAI's Astral Claude Code plugin repository](https://github.com/astral-sh/claude-code-plugins)
* [Anthropic pyright LSP plugin](https://claude.com/plugins/pyright-lsp)
