- Claude Code’s expanded lifecycle hooks shift control from fragile prompting to deterministic enforcement at specific points in the agent loop.
- Hooks run your code with event JSON, letting you allow/deny actions, rewrite tool arguments, or inject context—without the model being able to ignore it.
- The Stop hook is especially powerful: it can block the agent from finishing until your test suite passes, turning “definition of done” into an automatic gate.
- The same stop-until-green pattern can be applied to subagents (SubagentStop) so partial or unverified work can’t be handed back as complete.
- PreToolUse acts as a bouncer for tool calls (block or rewrite unsafe commands), while PostToolUse can sanitize or enrich outputs before the model sees them.

Claude Code now ships more than twenty lifecycle hooks, and one of them lets you refuse to let the agent finish until your test suite passes.
A hook is deterministic code you run at a fixed moment in the agent's loop. It is the layer where you stop trusting the model and start enforcing.
The instinct is to wrap an agent in a better prompt. The control you actually want lives in the event system, not the system prompt.
What changed
The June 5 build of Claude Code (CLI v2.1.165) brought the hook surface to more than two dozen event types. The list reads like the trace of an agent's life: PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, SessionStart, SessionEnd, Stop, SubagentStart, SubagentStop, PreCompact, PostCompact, PermissionRequest, PermissionDenied, TaskCreated, TaskCompleted, CwdChanged, FileChanged, MessageDisplay.
Every one of those fires a script you control. The script gets the relevant JSON on stdin, runs whatever you want, and answers back. It can stay quiet and let the agent proceed. It can deny the action. It can rewrite the action before it runs. It can inject text into the agent's context. The agent never sees the machinery, it just experiences a world where certain things are not allowed and certain facts are always present.
That is a different thing from prompting. A prompt is a request the model can ignore, misread, or forget after forty tool calls. A hook is code. It runs the same way every time, and the model does not get a vote.
The Stop hook is the interesting one
When the main agent decides it is done and tries to end its turn, the Stop event fires. Your hook can return a block decision with a reason, and the agent has to keep working.
So the pattern is exactly what the title says. The Stop hook runs your test suite. If the suite passes, the hook stays silent and the agent finishes. If it fails, the hook returns block with the failing output as the reason, and the agent reads its own broken tests and goes back in.
#!/bin/bash
if ! npm test --silent > /tmp/testout 2>&1; then
jq -n --arg r "$(tail -40 /tmp/testout)" \
'{decision: "block", reason: ("Tests are red. Fix before stopping:\n" + $r)}'
fi
exit 0
No more agent that declares victory on a build it never ran. The definition of done stops being a thing you hope the model internalized and becomes a thing the harness enforces on every turn. The same trick works on SubagentStop, so a subagent cannot hand back a half-finished result that the parent then trusts.
This maps onto something I have written about before. The human's two jobs around an agent are setting the bar and holding it. The prompt is where you set the bar. The Stop hook is where you hold it, in code, automatically, every single time the agent reaches for the exit.
PreToolUse is the bouncer
The Stop hook catches bad endings. PreToolUse catches bad actions before they happen.
Every tool call passes through PreToolUse first. The hook sees the tool name and its full arguments, and it answers with one of allow, deny, ask, or defer. It can also hand back a modified set of arguments, which means it does not only block, it edits.
The obvious use is a guardrail. Match a destructive shell command, deny it, done. But the rewrite path is where it gets useful. A PreToolUse hook can quietly add a flag the model keeps forgetting, redirect a write away from a protected path, or strip a dangerous option out of a command the model otherwise got right. The agent asked to do one thing, the hook did a safer version, and the loop continued without a round trip.
PostToolUse closes the loop on the other side. It sees the result and can transform it before the model reads it, or attach extra context. Truncate a 50,000-token log down to the error lines. Tag a file read with a note that this module is deprecated. The model only ever sees the version you allow it to see.
I run two of these every day
This is not hypothetical. The workspace I am writing this in has a SessionStart hook and a Stop hook wired up, and they change how every session behaves.
The SessionStart hook pulls this project's stored memories out of a local store and injects them as context before I type anything. I do not have to ask the agent to remember last week. The relevant decisions are already in the window when the session opens, because a script put them there. SessionStart supports an additionalContext field for exactly this, and that is all it takes.
The Stop hook is git-gated. When a session ends with new commits on the branch, it writes a one-line breadcrumb of what shipped into the same memory store. Next session, SessionStart reads it back. Two small scripts, and the agent has a working memory across sessions that no prompt could give it, because prompts do not persist and files do.
Neither hook is clever. That is the point. The leverage is not in the code, it is in the position. The code sits at a fixed event in the agent's loop and runs without fail.
Why this matters more than the next model
The token-cost posts and the benchmark posts get the attention, but the hook surface is the quieter story, and I think it is the bigger one for people shipping real work.
A more capable model makes the agent better at the middle, the part where it reasons and writes and calls tools. It does nothing for the edges, the moment before an action and the moment before it stops. Those edges are where production breaks. An agent that is 95 percent reliable on its own is an agent that does the wrong thing one call in twenty, and at the scale people now run agents, one in twenty is constant.
Hooks are how you take a 95 percent reliable model and put a deterministic floor under it. The model still does the creative middle. The script catches the predictable failures at the boundary, the rm that should never run, the stop on a red suite, the missing flag, the context that should always be present.
If you are evaluating Claude Code against Codex or anything else right now, the benchmark scores are within a couple of points of each other and that gap is mostly noise. The thing worth comparing is the control surface. How many places can you insert your own deterministic code, and how much can that code actually change. On that axis the answer moved a lot this month, and most people have not looked.
Open the hooks docs. Find the three events that map to your worst recurring failure. Write the scripts. The agent will behave better tomorrow, and not because the model got smarter.
If you use Claude Code (or any agent framework with lifecycle events), move your reliability guarantees out of the prompt and into hooks: add a Stop hook that runs your tests and blocks completion with the failing output until they’re green. Then add a PreToolUse hook to prevent or automatically rewrite risky commands and a PostToolUse hook to trim noisy logs or attach critical context. Treat hooks as your enforcement layer—set expectations in the prompt, but make the harness hold the line every time.