Skip to content

How to configure Claude Code with a Python type checker

Claude Code 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 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 gate so the same checks run for every commit, agent or human

This guide walks all four for ty, mypy, and pyright. The shape is the same in every case; only the commands and the plugin source change.

Prerequisites

  • Claude Code installed
  • uv 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 (Astral) 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:

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 Astral’s plugin for ty

Astral publishes an official Claude Code plugin 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 the Astral plugins for Claude Code for the team-wide install via .claude/settings.json.

Install Anthropic’s pyright LSP plugin

Anthropic publishes an official pyright LSP plugin. 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:

.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 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.

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 Astral does not yet publish an official pre-commit hook repo (tracking issue):

.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 repo, pinned to a recent release tag:

.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:

.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 (a faster reimplementation of pre-commit) or pre-commit itself:

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.

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:

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 Astral'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:

.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:

.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. It bundles type checking, linting with Ruff, testing with pytest, and uv invocation rules into one file.

Learn More

Last updated on