Where the PostToolUse hook fires
Claude Code hooks are shell commands that the CLI runs at fixed points in a session. The hook that matters for this workflow is PostToolUse, which fires after a tool call finishes and before the model reads the result. Put it under the hooks key in your settings.json, give it a matcher, and every match runs the commands you list.
The matcher is a regular expression tested against the tool name. For an edit-and-test loop you want only the tools that touch source files. A narrow matcher keeps the suite from firing when the agent lists directories, greps the repo, or reads a log.
- Edit and MultiEdit cover in-place changes to existing files.
- Write covers new files, which is where fresh test files usually appear.
- Leaving out Bash and Read keeps shell commands and inspection out of the test path.
Each matcher entry holds a list of hook objects, each with a type of command and a command string. The string is a shell line, so it can set a working directory, pick the test runner, and pull the changed file path out of the tool input JSON that the hook receives on standard input.
Two configurations side by side
Start with the two shapes people actually ship. The first runs one broad command after every write: a single matcher that groups the editing tools, with one command that runs the whole suite. It is simple to read, simple to delete when it annoys you, and it gives the model one verdict per edit.
The second splits the work. One matcher targets Edit and MultiEdit with a command that derives the test file from the edited file and runs just that test. A second matcher targets Write, where new files land, and runs the containing directory. A third entry can run the wider suite only when a manifest or lockfile changes, since those edits can break anything.
- Broad entry: one command, one verdict, and a longer pause after every edit.
- Split entries: short targeted runs after most edits, wider coverage on file creation.
- Both keep the same rule: the test run is a gate the model must respect, not a suggestion.
The split layout is more settings to maintain, and it scales better as the suite grows, because most edits stop paying for tests that cannot possibly be affected by them. The broad layout stays attractive for small packages where the entire suite is quick and you want the simplest possible file.
What the exit code decides
A command that exits with 0 tells Claude Code that everything is fine and the session continues. A command that exits with 2 marks the tool call as failed and feeds the standard error back to the model, so the failure becomes part of its working context. Other exit codes end the command without changing the tool result.
That is the lever that makes the hook useful. Fail fast on a broken build or a failing test, and the model reads the error while the edit is still fresh in its reasoning. Stay quiet on success so the transcript is not flooded with passing output.
- Print failing test names and the assertion message to standard error.
- Keep successful output short, so the context window is not filled with green checkmarks.
- Give the command its own timeout, because a hung test run should not stall the session.
Remember that the hook inherits your shell environment. If the runner needs an API key, a virtual environment, or a particular toolchain, export it inside the command itself or in the shell profile the hook sources.
A checklist before the green light
- The matcher list names only the tools that change source.
- The command runs from the project root and fails clearly when the test file is missing.
- Failing output goes to standard error and includes the test name.
- The run has both a timeout and an explicit working directory.
- One command runs the narrow test, and a second, on-demand command runs the wider suite.
Test the hook on purpose before you trust it. Make a deliberate syntax error, trigger an edit, and confirm the failure reaches the model and that the model repairs it. Then disable the hook and run the same edit, so you know the loop is doing the work and not your own habits.
Which configuration earns the green light
Take the split configuration. Use a narrow PostToolUse matcher on the editing tools, run the targeted test after each edit, exit with 2 on failure, and keep the wider suite available as a separate command. That arrangement catches regressions while the change is still small, and it keeps the agent honest about every file it touches.
Run the loop on a model you are comfortable leaving to iterate. A fast, inexpensive model is the practical pick for an edit that may trigger many test runs in one session, such as Claude Haiku 4.5 or Gemini 2.5 Flash, and you can hand the tougher reasoning to a larger model when the suite turns red. SwiftRelay is a pay-as-you-go relay for Claude, GPT, Gemini, DeepSeek, Kimi, GLM, Qwen, Grok and other models, it works with Claude Code, Codex, Gemini CLI and any OpenAI-compatible SDK, and billing is per token. Point Claude Code and Gemini CLI at the bare host https://api.fastapirelay.com, and add /v1 when you use an OpenAI SDK. Sign up at SwiftRelay, claim the small gift credit on new accounts, and let your hook run against the relay today, and inviting friends earns you 14% of their top-ups.