Make Claude Code prove it’s done
This is the setup from the reel. Every time Claude Code finishes a reply, it runs your tests. If they fail, Claude isn’t allowed to stop: it gets the failures back and keeps working.
1. Add the hook
Open (or create) .claude/settings.json in your project and add this:
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": "npm test 1>&2 || exit 2"
}]
}]
}
}
Not using npm? Swap npm test for your own test command, like pnpm test, pytest or go test ./.... Keep the 1>&2 || exit 2 part.
2. Why it works
- A Stop hook runs when Claude Code finishes responding.
- If the command exits with code 2, Claude is blocked from stopping, and it receives the command’s error output as the reason to keep going.
|| exit 2turns “tests failed” into that exit code.1>&2matters more than it looks: Claude only sees the error output (stderr), and most test runners print failures to normal output (stdout). Without it, Claude gets blocked but never sees what failed.
3. Check it’s on
Break one test on purpose, then ask Claude Code for any small change. When it finishes, the hook runs, the test fails, and you’ll see Claude pick the failure back up instead of saying it’s done. Fix the test back afterwards.
Good to know
- It runs after every reply, even a quick question. If your full suite is slow, point the hook at a fast subset of tests.
- It doesn’t run if you interrupt Claude yourself.
- It keeps going while Claude keeps editing. If a test can’t pass at all (a missing env var, a database that’s down), Claude can keep trying for a long time and use up your limits. Claude Code only forces a stop after 8 blocks in a row with no tool use in between. Keep an eye on the first few runs.
- It’s a bash command. It works on macOS, Linux and Windows with Git Bash.
- Team project?
.claude/settings.jsoncan be committed so everyone gets it. For just you, use.claude/settings.local.jsonor~/.claude/settings.json(all your projects).
Full details: the Claude Code hooks reference.