HookStack
Back to catalogue
ValidationPostToolUse· Write|Edit

Pyright type checking

Python type errors caught right after an edit

Runs pyright via uv on the modified .py file and reports type errors to the agent so they are fixed within the same loop.

What does the Pyright type checking hook do?

Pyright type checking is a Claude Code PostToolUse hook matching Write|Edit. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Python type errors caught right after an edit.

As a PostToolUse hook it runs after the action, reacting to what just happened rather than blocking it. Because it is a deterministic Node.js script, it executes on every matching event without relying on the model to remember — the guarantee that makes agentic workflows safe to automate.

Use cases

  • Strict typing in Python projects with pyright
  • Catch type mismatches immediately after an edit
  • Pair with ruff for a complete Python quality pipeline

Tags

#validation#pyright#typecheck#python#quality

settings.json fragment

{
  "hooks": {
    "PostToolUse": [
      {
        "hooks": [
          {
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/pyright-check.mjs",
            "type": "command"
          }
        ],
        "matcher": "Write|Edit"
      }
    ]
  }
}

Script · .claude/hooks/pyright-check.mjs

#!/usr/bin/env node
// @hookstack post-edit-pyright
import { execSync } from "node:child_process";
// @hookstack post-edit-pyright
// Vérifie les types Python avec pyright après édition (PostToolUse Write|Edit)
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";

function defaultExec(cmd) {
	return execSync(cmd, { encoding: "utf8", stdio: "pipe", timeout: 30_000 });
}

export function run(input, { exec = defaultExec } = {}) {
	const filePath = input.tool_input?.file_path ?? input.tool_input?.path ?? "";
	if (!filePath.endsWith(".py")) return null;

	try {
		exec(`uv run pyright "${filePath}"`);
		return null;
	} catch (err) {
		const output = err.stdout?.toString() ?? "";
		return output ? { message: `[pyright] ${output.trim()}\n` } : null;
	}
}

/* v8 ignore next 5 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
	const input = JSON.parse(readFileSync(0, "utf8"));
	const result = run(input);
	if (result?.message) process.stderr.write(result.message);
}

Learn more

Related hooks