Back to catalogue
ValidationPostToolUse· Write|EditPostToolUseAfter tool execution · non-blocking· non-blocking
Ruff lint after write
Unused imports caught before they pile up
Runs ruff check --fix on the modified Python file so lint violations are reported to the agent immediately and auto-fixable rules are applied inline.
What does the Ruff lint after write hook do?
Ruff lint after write 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. Unused imports caught before they pile up.
Use cases
- Catch unused imports and undefined names before they accumulate
- Auto-fix trivial violations (isort, unnecessary pass, etc.)
- Enforce project ruff rules without a CI round-trip
Tags
#validation#ruff#lint#python#quality
settings.json fragment
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/ruff-check.mjs",
"type": "command"
}
],
"matcher": "Write|Edit"
}
]
}
}Script · .claude/hooks/ruff-check.mjs
#!/usr/bin/env node
// Analyse et auto-corrige le fichier Python avec ruff après écriture (PostToolUse Write|Edit)
import { readFileSync } from 'fs';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
function defaultExec(cmd) {
return execSync(cmd, { encoding: 'utf8', stdio: 'pipe', timeout: 15_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 ruff check --fix "${filePath}"`);
return null;
} catch (err) {
const output = err.stdout?.toString() ?? '';
return output ? { message: `[ruff-check] ${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);
}