Next-Gen App & Browser Testing Cloud
Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles

Hooks fire on lifecycle events rather than on model judgement, which makes them the one part of an agentic workflow you can actually guarantee.

Bhawana
Author

Shahzeb Hoda
Reviewer
Published on: August 27, 2026
You tell the agent, in CLAUDE.md, to run the formatter before it commits. It does, for eleven turns. On the twelfth it is deep in a refactor, the instruction is buried under forty thousand tokens of context, and it commits unformatted code.
Nothing malfunctioned. You wrote a suggestion and expected a guarantee.
Hooks are where that expectation belongs. They are the part of the workflow that does not consult the model.
TL;DR
Claude Code hooks are handlers the agent runs at fixed points in its own lifecycle, before a tool call, after one, or when a turn ends. They fire whether or not the model decides they should, which is what makes them the deterministic layer wrapped around a probabilistic agent.
A hook is a handler bound to a lifecycle event in the agent's own loop. When the event happens, the handler runs. The model is not asked whether it should.
That single property is the whole reason hooks exist. Instructions in CLAUDE.md are read by the model and weighed against everything else in context. A hook is executed by the runtime.
The surface is larger than most teams use. Anthropic's Claude Code hooks reference documents more than thirty distinct events, covering session lifecycle, prompt submission, every stage of a tool call, subagents, compaction, and file or configuration changes on disk.
That last distinction matters more than it looks. We mapped the prevention side separately in pre-action checks for AI coding agents, which compares hooks against permission modes, sandboxes, and branch rules.
All of them are available, but a small group carries almost every practical workflow. Grouping them by what they let you do is more useful than reading the full list top to bottom.
| Event | Fires when | What it is good for |
|---|---|---|
| SessionStart | A session begins or resumes | Injecting current branch, ticket, or environment state as context |
| UserPromptSubmit | You submit a prompt, before Claude processes it | Rejecting a prompt outright, or attaching context the request implies |
| PreToolUse | Before a tool call executes | Denying a destructive command, or rewriting its input |
| PostToolUse | After a tool call succeeds | Formatting a file that was just written, or linting the change |
| PostToolUseFailure | After a tool call fails | Feeding the real error back so the agent stops guessing |
| Stop | Claude finishes responding | Running the verification that decides whether the turn is actually done |
| SubagentStop | A subagent finishes | Checking a delegated unit of work before its result is trusted |
| PreCompact | Before context compaction | Persisting state that is about to be summarised away |
Two of these are worth singling out. PostToolUseFailure exists because an agent that cannot see the real error invents a cause and fixes the wrong thing.
Stop is the one most teams leave empty, and it is the only event that fires exactly when the agent believes it is finished.
Hooks live under a top-level hooks key in settings.json, keyed by event name. Each entry pairs a matcher with one or more handlers.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_FILE_PATHS\"",
"timeout": 60,
"statusMessage": "Formatting the file that just changed"
}
]
}
]
}
}The matcher accepts three forms, and picking the wrong one is the usual reason a hook never fires.
A handler does not have to be a shell command. Five handler types are supported, and the choice changes what the hook can reach.
Note what the last two mean for determinism. A prompt or agent handler puts a model back inside the control path, so the hook fires reliably but its verdict does not have to be identical twice.
Configuration can come from user settings, project settings, gitignored local settings, managed policy, plugin bundles, and skill or subagent frontmatter. Hooks merge across those levels rather than overriding one another, so a project hook adds to your personal ones instead of replacing them.
Note: TestMu AI's Kane CLI gives a hook something worth running: a plain-English objective, real Chrome, and a pass or fail. Try TestMu AI free!
A command hook communicates through its exit status, and only one value blocks anything.
| Exit code | Effect | Where the message goes |
|---|---|---|
| 0 | Success. Standard output is parsed for JSON control fields | Plain text goes to the debug log, except on UserPromptSubmit, UserPromptExpansion, and SessionStart, where it is added to Claude's context |
| 2 | Blocking error. The action is blocked regardless of any JSON printed | Standard error becomes the blocking message the agent reads |
| Any other non-zero | Non-blocking error. Execution continues | Ignored entirely if the printed JSON passes schema validation |
The third row is the one that bites. A script that exits 1 on failure, which is the convention almost every command-line tool follows, does not block anything in a hook.
For finer control, exit 0 and print JSON instead. A PreToolUse hook can return a hookSpecificOutput object carrying permissionDecision set to allow, deny, or escalate, a permissionDecisionReason string, additionalContext for the model, and an updatedInput object that rewrites the tool call before it runs.
Yes, and this is where hooks stop being housekeeping. Nearly every hook example in circulation formats a file, writes a log line, or plays a sound. None of those answer whether the change works.
The Stop event fires when Claude finishes responding, which is precisely the moment its claim of done is worth testing.
A unit test run from that hook still only proves the code agrees with itself. To prove the interface works, something has to open the page. Kane CLI from TestMu AI is built for exactly that call: an objective in plain English, a real Chrome browser, and a machine-readable verdict.
Here is a real run of that shape against the TestMu AI Selenium Playground, on Kane CLI 0.8.4. Only the final line matters to a hook script, because that is the line the verdict lives on.
$ kane-cli run --agent --headless \
"open the Simple Form Demo, type 'hooks gate' into the message field,
click Get Checked Value, and assert the page shows 'hooks gate'" \
--url https://www.testmuai.com/selenium-playground/ | tail -1
{"type":"run_end","status":"passed",
"summary":"Entered 'hooks gate' in the message field and clicked the Get Checked
Value button. The page displayed 'hooks gate' as the checked message.",
"final_state":{"url":"https://www.testmuai.com/selenium-playground/simple-form-demo/",
"checked_message":"hooks gate"},
"reason":"Objective completed","duration":41.6,"bifurcated":false,"total_runs":1}
$ echo $?
0Note the assertion was never written as a selector. The objective said what the page should show, and the run recorded which element it read that value from.
Wiring it to the Stop event takes one handler.
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": ".claude/verify.sh",
"timeout": 420,
"statusMessage": "Verifying the change in a real browser"
}
]
}
]
}
}Installation is a single npm command, and the tool needs Node.js 18 or higher with Google Chrome on the PATH. The full command reference lives in the Kane CLI introduction documentation.
Here is the trap in wiring any real verifier to a hook, and it is silent when you get it wrong.
Both systems use small integer exit codes, and they do not mean the same things. Kane CLI returns 0 for a pass, 1 for a failed assertion, 2 for an environment error such as an authentication failure or a Chrome crash, and 3 for a timeout.
Claude Code reads 2 as block and treats 1 and 3 as non-blocking noise. Pass the verifier's status straight through and you get the exact inversion of what you wanted.
| Kane CLI exits | What actually happened | Passed through raw, the hook | What you wanted |
|---|---|---|---|
| 0 | The check passed | Allows the turn to end | Allow |
| 1 | An assertion failed, the feature is broken | Logs a non-blocking error and ends the turn anyway | Block |
| 2 | Chrome crashed or authentication failed | Blocks and tells the agent its code is wrong | Report an environment problem, not a code failure |
| 3 | The run timed out | Ends the turn as though nothing was checked | Block, or retry once |
The fix is a translation layer of about ten lines. Never pipe one tool's exit status into another tool's exit contract.
#!/usr/bin/env bash
# .claude/verify.sh - translate Kane CLI status into hook semantics
kane-cli run --agent --headless \
"sign in as the demo user, open the dashboard,
assert the revenue widget renders a number and not an error state" \
> /tmp/kane.ndjson 2>/tmp/kane.err
status=$?
case $status in
0) exit 0 ;; # verified, let the turn end
1) echo "Browser check failed. Fix the feature, do not adjust the assertion." >&2
tail -1 /tmp/kane.ndjson >&2
exit 2 ;; # 2 is the only code that blocks
2) echo "Kane CLI could not run: environment or auth problem, not your code." >&2
exit 1 ;; # non-blocking, surfaces without accusing
3) echo "Verification timed out before it could decide anything." >&2
exit 2 ;;
esacLine four of that case block is the one that matters. Telling an agent its code is broken when Chrome simply failed to launch sends it off to rewrite something that was already correct.
A hook guarantees that something runs. It guarantees nothing about what that something concludes.
The timeout case deserves a specific defence, because it is the one that degrades quietly. Log every hook invocation to a file and watch for the day the count stops rising.
The deeper limit is structural rather than technical. A hook you configured checks what you already thought to check, which is the same blind spot we traced in whether coding agents can test their own code.
Note: TestMu AI's Kane CLI returns standard POSIX exit codes, so a GitHub Actions step fails the build with no wrapper script. See the GitHub Actions setup
Adding thirty hooks on day one produces a slow agent and a config nobody understands. Four earn their place immediately.
Start with the first one, because it is the cheapest way to confirm your configuration is even being loaded. Add the fourth once the first three are quiet.
Then treat the hook config as code. It sits in the repository, it changes behaviour, and it deserves the same review as anything else that can block a merge.
For the wider pattern this fits into, from the agent loop through to production, our walkthrough of continuous verification for AI-generated code covers each stage in order. And when the work is split across several agents rather than one, Claude Code agent teams covers how the same gates apply per teammate.
Author
Bhawana is a Community Evangelist at TestMu AI with over 3 years of experience creating technically accurate, strategy-driven content in software testing. She has authored 50+ blogs on test automation, cross-browser testing, mobile testing, and real device testing. She also serves as Product Marketing Manager for Kane CLI, the command-line tool that runs browser automation from the terminal using natural-language flows in a real Chrome browser. Bhawana is certified in KaneAI, Selenium, Appium, Playwright, and Cypress, reflecting her hands-on knowledge of modern automation practices. On LinkedIn, she is followed by 6000+ QA engineers, testers, AI automation testers, and tech leaders.
Reviewer
Shahzeb Hoda is the Associate Director of Marketing and a Community Contributor at TestMu AI, leading strategic initiatives in developer marketing, content, and community growth. With 10+ years of experience in quality engineering, software testing, automation testing, and e-learning, he has authored and reviewed 70+ technical articles on software testing and automation. Shahzeb holds an M.Tech in Computer Science from BIT, Mesra, and is certified in Selenium, Cypress, Playwright, Appium, and KaneAI. He brings deep expertise in CI/CD pipeline automation, cross-browser testing, AI-driven testing practices, and framework documentation. On LinkedIn, he is followed by 3,700+ engineers, developers, DevOps professionals, tech leaders, and enthusiasts.
Did you find this page helpful?
More Related Blogs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance