Skip to content

How to configure Claude Code to run your pytest suite

Give Claude Code a repeatable command for the project’s pytest suite. Use an on-demand skill for targeted runs and an optional bounded Stop hook for a final check.

Prerequisites

  • Claude Code and uv installed
  • A uv project with pytest installed as a development dependency and an existing passing test suite

uv run executes the command inside the project’s virtual environment, ensuring all dependencies are installed first.

Add a test skill

Create .claude/skills/test/SKILL.md:

.claude/skills/test/SKILL.md
---
name: test
description: Run the project's pytest suite or a selected test path and diagnose failures.
argument-hint: "[pytest path or options]"
---

Run `uv run pytest --tb=short -q $ARGUMENTS`.
Read failures and fix their cause. Do not change expected behavior just to
make a test pass. Rerun failures with `uv run pytest --last-failed -q --tb=short`,
then run the full suite with `uv run pytest -q --tb=short`.
Report commands, results, and any remaining failures.

Invoke /test for the whole suite or /test tests/test_api.py for one file. Claude can also select the skill when a request matches its description. The older .claude/commands/test.md location remains supported; avoid defining both under the same name.

--tb=short shortens failure tracebacks. -q reduces collection and progress detail while retaining failures and the summary. --last-failed reruns previously failing tests, so follow it with the full suite before declaring success.

Run a final check with a bounded hook

Create .claude/hooks/check-tests.py:

.claude/hooks/check-tests.py
import json
import os
import subprocess
import sys

payload = json.load(sys.stdin)
# Let Claude stop after one corrective attempt; report remaining failures.
if payload.get("stop_hook_active"):
    sys.exit(0)
result = subprocess.run(
    ['uv', 'run', 'pytest', '-q', '--tb=short'],
    cwd=payload.get("cwd") or os.environ.get("CLAUDE_PROJECT_DIR") or ".",
    capture_output=True, text=True,
)
if result.returncode:
    print(result.stdout + result.stderr, file=sys.stderr)
    print("Fix the failure or report why verification is blocked.", file=sys.stderr)
    sys.exit(2)

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

.claude/settings.json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "uv",
            "args": [
              "run",
              "--no-project",
              "python",
              "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-tests.py"
            ],
            "timeout": 120
          }
        ]
      }
    ]
  }
}

The handler uses the payload’s active directory so pytest checks the current worktree. --no-project launches the standard-library handler; its internal uv run pytest command uses the active project’s dependencies.

When pytest fails, exit 2 sends the failure report to Claude and asks it to continue. The stop_hook_active guard allows the next stop after one corrective attempt, so persistent failures do not create a loop. The agent must report any failure that remains; this hook does not guarantee a passing suite.

Run only fast, reliable tests here. Scope the command to a test directory or marker if the full suite needs network services or takes too long. An empty test selection also fails because pytest exits 5 when it collects no tests.

Confirm the result and retain CI checks

Open /hooks in Claude Code to confirm the hook loaded. Ask Claude to run /test, then inspect its reported command and summary. A passing run ends with a result such as 5 passed; for a failing run, confirm that the hook returns the traceback and that the agent either fixes the cause or reports the blocker.

Keep uv run pytest in CI. Stop hooks operate only in configured Claude sessions and can be disabled or skipped after the corrective attempt. Required CI checks verify the final commit independently.

Last updated on