Telling a coding agent "run the tests after you change something" works most of the time. A hook makes it work every time, and it costs about a dozen lines.
Claude Code fires PostToolUse hooks after a tool call finishes. If you match on the file-editing tools, you get a reliable "after every edit" moment where you can run a fast check and hand the result straight back to the agent.
The hook
#!/usr/bin/env python3
import json, subprocess, sys
event = json.load(sys.stdin)
path = event.get("tool_input", {}).get("file_path", "")
# Only react to source files; skip docs and lockfiles.
if not path.endswith((".ts", ".tsx", ".py")):
sys.exit(0)
result = subprocess.run(
["npm", "test", "--silent", "--", "--bail"],
capture_output=True, text=True, timeout=120,
)
if result.returncode != 0:
print("Tests failed after this edit:\n" + result.stdout[-1500:], file=sys.stderr)
sys.exit(2) # blocking status: the failure goes back to the agent
Swap npm test for pytest -x -q or whatever your fastest suite is. Keep it fast: if a check takes longer than the edit, the agent spends the session waiting.
Wire it up
In .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [{ "type": "command", "command": "python3 .claude/hooks/test_after_edit.py" }]
}
]
}
}
Why this beats an instruction
- The agent sees the failing output immediately, in the same turn, instead of discovering it three edits later.
- It never "forgets" on a long session.
- Your project instructions stay short, because the rule lives in code.
Keep the list short
Two or three hooks is plenty: a pre-edit guard for dangerous commands, a post-edit test run, and maybe a formatter. Past that, every edit gets slow and noisy.
If you want a ready-made set (guard hook, test-after-edit hook, settings file and a short CLAUDE.md), the Vildanden Claude Code Ops Pack bundles them. The free Agent Config sample is a good place to start if you just want the house-rules file.
Top comments (1)
One gap in the "after every edit" guarantee: the matcher is "Edit|Write|MultiEdit", so it only sees edits made through those tools. The hooks reference says Claude Code does not run a PostToolUse hook matching Edit|Write when a Bash command rewrites the same file. A sed -i, a codegen script or a formatter started from Bash changes source files and this hook stays silent. The reference gives two ways out: omit the matcher and let the script find what changed with git status --porcelain, or use the FileChanged event for specific files. With no matcher, the file_path suffix filter in your script needs another source for the changed paths, because a Bash call has no file_path.