Skip to content

How to configure Ruff with Claude Code

Give Claude Code the Ruff commands your project uses, then add an edit hook if you want formatting to run automatically. Keep a commit or CI check to cover changes made outside Claude Code.

This guide covers all four and how they stack together:

  • A CLAUDE.md instruction that points Claude at Ruff
  • Astral’s plugin’s /astral:ruff skill for on-demand Ruff guidance
  • A PostToolUse hook that auto-formats every Python edit
  • A pre-commit gate that catches anything Claude missed before commit time

Tip

Starting a new project? The project setup tutorial builds all four layers on a fresh uv package from scratch.

Prerequisites

  • Claude Code installed
  • uv installed
  • A Python project with Ruff added as a dev dependency (uv add --dev ruff)

Pick the right layer for your goal

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

Goal Layer
Tell Claude which Ruff commands to prefer and to invoke them through uv run CLAUDE.md
Give Claude up-to-date Ruff usage guidance on demand (rule selection, migrating from Black or flake8) /astral:ruff skill
Auto-format every Python file Claude writes or edits PostToolUse hook
Catch unfixable lint errors before they reach the repo, even when Claude is not the one committing pre-commit

Tell Claude Code to use Ruff with CLAUDE.md

A CLAUDE.md file in your project root is the simplest place to point Claude at Ruff. Claude loads it as session context, so a few lines name the exact commands it should use.

Add this section to your CLAUDE.md:

CLAUDE.md
## Linting and formatting

This project uses Ruff for both linting and formatting. Do not call Black,
flake8, isort, or pylint.

- Lint: `uv run ruff check .`
- Lint and auto-fix: `uv run ruff check --fix .`
- Format: `uv run ruff format .`
- Check formatting without writing: `uv run ruff format --check .`
- Always invoke Ruff through `uv run` so it resolves to the project's
  virtual environment.

Ruff configuration lives in `pyproject.toml` under `[tool.ruff]`. Do not
add a separate `ruff.toml` or `.ruff.toml`. Do not add inline `# noqa`
comments without a rule code.

Anthropic’s CLAUDE.md best practices recommend a pruning test for every line: would Claude make this mistake without the instruction? Each line above passes that test. Keep instructions that address mistakes observed in your project; remove rules that do not affect its workflow.

Tip

If you want a starter that already includes Ruff alongside uv, pytest, and pre-commit guidance, use the CLAUDE.md template for Python projects instead of writing one from scratch.

For projects that don’t yet have a curated Ruff configuration, see How to configure recommended Ruff defaults; Claude will follow whatever rules pyproject.toml declares.

Add Astral’s plugin’s Ruff skill

Astral, now part of OpenAI, publishes a Claude Code plugin that bundles /astral:uv, /astral:ruff, and /astral:ty. The Ruff skill teaches Claude Ruff’s current lint and format workflow, plus migration from older formatter and linter setups. The skill body only loads when invoked, so it doesn’t compete with CLAUDE.md for the always-on context budget.

Inside Claude Code, run:

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

Once installed, ask Claude to “format this module with /astral:ruff” or “migrate this Black config to Ruff using /astral:ruff” and it follows the plugin’s documented workflow for the tool.

To make Claude reach for the skill automatically when you mention Ruff, add this line to your CLAUDE.md:

CLAUDE.md
When working with Ruff, invoke `/astral:ruff` to follow the plugin's
usage guidance.

For a team-wide install via .claude/settings.json so every collaborator gets the plugin after trusting the repository, see How to install Astral’s plugins for Claude Code.

Auto-format every Python edit with a hook

A PostToolUse hook can format a file after Claude’s Write or Edit tool succeeds. It cannot prevent the original edit or catch every shell command that writes a file.

Create .claude/hooks/ and save this script:

.claude/hooks/ruff-after-edit.py
import json
import os
from pathlib import Path
import subprocess
import sys

payload = json.load(sys.stdin)
root = Path(payload.get("cwd") or os.environ.get("CLAUDE_PROJECT_DIR") or ".")
path = Path(payload.get("tool_input", {}).get("file_path", ""))
if not path.is_absolute():
    path = root / path
if path.suffix not in {".py", ".pyi"} or not path.is_file():
    sys.exit(0)

failed = False
for args in (["check", "--fix", "--force-exclude"], ["format", "--force-exclude"]):
    result = subprocess.run(
        ["uv", "run", "ruff", *args, str(path)],
        cwd=root, capture_output=True, text=True,
    )
    if result.returncode:
        print(result.stdout + result.stderr, file=sys.stderr)
        failed = True
sys.exit(2 if failed else 0)

Register it in .claude/settings.json, merging the event with any existing hooks:

.claude/settings.json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "uv",
            "args": [
              "run",
              "--no-project",
              "python",
              "${CLAUDE_PROJECT_DIR}/.claude/hooks/ruff-after-edit.py"
            ],
            "timeout": 30
          }
        ]
      }
    ]
  }
}

The script uses Ruff from the payload’s active directory, so it follows the current worktree’s environment and configuration. It applies safe lint fixes and formats .py and .pyi files. --force-exclude honors configured exclusions for the explicit path. If either command fails, exit 2 returns the diagnostics to Claude; the edit remains on disk.

The command and args form starts uv directly on macOS, Linux, and Windows. --no-project launches the standard-library handler without synchronizing the surrounding project; the handler’s uv run ruff commands use the active project’s dependencies.

Open /hooks to confirm registration, then ask Claude to edit an unformatted Python file. Inspect the diff and run uv run ruff check . and uv run ruff format --check .; both should pass. For a bounded final check with another tool, see write Claude Code hooks for Python projects.

Run Ruff in pre-commit before commits

Hooks fire while Claude is editing; pre-commit fires when anyone (Claude included) tries to commit. The two are complementary. Pre-commit catches the case where Claude bypasses or misconfigures its own hooks, and it also gates contributions from teammates who aren’t using Claude at all.

Astral maintains the ruff-pre-commit repo. Add this to .pre-commit-config.yaml at your project root, replacing the rev: with the latest release tag:

.pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.17.2
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format

Install the hooks once with prek (the faster reimplementation of pre-commit) or pre-commit itself:

uvx prek install
# or
uvx pre-commit install

Now git commit runs Ruff before recording the commit. If Ruff fixes anything, the commit aborts and you re-stage the changes; if it reports an unfixable error, you address it before the commit can complete.

When Claude commits a change (with git commit in the bash tool), the same hook runs. If pre-commit modifies files, Claude sees the abort message and can stage the fixes on the next turn.

For the full pre-commit setup including mypy, ty, and other Python tools, see How to set up pre-commit hooks for a Python project.

Keep the workflow verifiable

Commit CLAUDE.md, .claude/settings.json, the hook script, and .pre-commit-config.yaml. The skill supplies usage guidance, the hook formats supported edits, and pre-commit checks normal commits. Required CI checks cover commits created without local hooks.

Last updated on