Back to catalogue
ValidationStopStopWhen the agent finishes its task· non-blocking
Run pytest at end of response
Won't hand back until pytest is green
When the agent finishes, runs the full pytest suite via uv. If tests fail, Claude re-enters and receives the failure output as context to fix the regressions.
What does the Run pytest at end of response hook do?
Run pytest at end of response is a Claude Code Stop hook. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Won't hand back until pytest is green.
Use cases
- Regression safety net on every agent turn
- TDD loop: write code until tests go green
- Catch side-effects of refactors before control returns to the user
Tags
#validation#pytest#uv#python#tests#ci
settings.json fragment
{
"hooks": {
"Stop": [
{
"hooks": [
{
"command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/pytest.mjs",
"type": "command"
}
]
}
]
}
}Script · .claude/hooks/pytest.mjs
#!/usr/bin/env node
// Exécute pytest à la fin d'une session Python (Stop)
import { existsSync } from 'fs';
import { spawnSync } from 'child_process';
import { fileURLToPath } from 'url';
const PYTHON_MARKERS = ['pyproject.toml', 'setup.py', 'pytest.ini', 'setup.cfg'];
export function run({
exists = existsSync,
spawn = spawnSync,
cwd = process.env.CLAUDE_PROJECT_DIR ?? process.cwd(),
} = {}) {
const isPython = PYTHON_MARKERS.some(f => exists(`${cwd}/${f}`));
if (!isPython) return null;
const result = spawn('uv', ['run', 'pytest', '--tb=short', '-q'], {
encoding: 'utf8',
timeout: 120_000,
cwd,
stdio: ['ignore', 'pipe', 'pipe'],
});
const out = (result.stdout ?? '') + (result.stderr ?? '');
const status = result.status ?? 1;
const message = status !== 0
? `[pytest] ÉCHEC (exit ${status})\n${out.slice(-2000)}\n`
: `[pytest] ✓ Tests passés\n${out.split('\n').filter(Boolean).slice(-5).join('\n')}\n`;
return { status, message };
}
/* v8 ignore next 6 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const result = run();
if (result) {
process.stderr.write(result.message);
if (result.status !== 0) process.exit(2);
}
}