The Claude Code hooks reference has ballooned to 30+ events in 2026. PostToolUseFailure, WorktreeCreate, PermissionDenied, Elicitation, TaskCreated -- the list keeps growing as Anthropic ships finer-grained lifecycle instrumentation.
That reference is exhaustive. What it is not is a diagram. If you scroll the hooks page top to bottom you get 30 alphabetized entries with matcher fields and JSON schemas. You do not get the picture of when in the agent's life each one fires, which is the thing you need to know before you decide where to hook.
The core lifecycle is 8 stages. Every Claude Code session moves through them in the same order, every turn. The other 20-plus events are either sub-events, error variants, or newer instrumentation that hangs off the core 8. If you can see the 8 in your head, you can figure out where to hook without re-reading the docs each time.
Anthropic has not shipped this diagram. I have been staring at hook JSON logs for six months building a self-improving harness. This is the picture I ended up with, plus what each of the 8 hooks can and cannot block.
Why Anthropic ships the reference but not the diagram
The reference-vs-diagram gap is not sloppiness. It is deliberate. Anthropic is optimizing hooks for SDK-level composability -- each hook has a clean input schema and a clean exit code contract, and the docs treat them as independent primitives. That is the right thing to do at the API layer.
The cost is that hook users have to build the mental model themselves. Which hook fires before which. Which ones can block. Which ones get replayed on session resume. What the SubagentStop vs Stop distinction really means at runtime.
I wrote this diagram for myself after the third time I put a hook in the wrong stage and shipped a subtle bug (PostToolUse on Edit where I should have used PreToolUse -- it ran the linter after the file was written but had no way to stop the tool call that just committed the bad code).
The 8 hooks, in lifecycle order
Every session starts at 1, hits some subset of 2-7 depending on what the user asks for, and ends at 8. That is the whole loop.
1. SessionStart -- when a new session begins (or resumes)
Fires exactly once at the top of every session. On session resume via claude --continue it re-fires with source: "resume", and on --fork-session it fires with source: "fork". This is the hook you want for context that has to be refreshed every turn but should not be inlined in CLAUDE.md.
Common uses: inject a "today's date is X" snippet, load a rotating tip, refresh a cached API key, run a "what changed in the repo since last session" summary.
Can block: no. There is no "block" semantics for SessionStart. Non-zero exit codes are logged but the session still starts.
2. UserPromptSubmit -- when the user submits a prompt
Fires after the user hits enter, before Claude sees the prompt. This is where you validate user input, expand shortcuts, inject dynamic context, or refuse to send the prompt at all.
Common uses: reject prompts that mention forbidden strings (customer names, API keys), expand @last-error into the actual last-error text, prepend a system snippet based on the current directory.
Can block: yes. Exit code 2 blocks the prompt from reaching Claude. The user sees the hook's stderr as an error message.
3. PreToolUse -- before Claude calls any tool
The most-hooked event. Fires immediately before every tool invocation, once per tool. The matcher field is critical here -- you almost always want matcher: "Bash" or matcher: "Edit|Write", not a global hook that fires on every Read.
Common uses: block rm -rf, block git push --force to protected branches, require confirmation for destructive commands, log every Bash command for audit.
Can block: yes. Exit 2 blocks the tool call. Claude gets the stderr back as the tool result, which it can then react to (usually by retrying with a different command).
4. PostToolUse -- after a tool call succeeds
Fires after every successful tool invocation. Note the "successful" -- if the tool errored, PostToolUseFailure fires instead. This asymmetry catches people the first time they hook PostToolUse for cleanup and wonder why it does not run on error paths.
Common uses: run the linter after Edit, run tests after Bash if the command was a test invocation, format the file after Write, index the change in a vector DB.
Can block: no in the "undo the tool" sense (the file is already written), but yes in the "make Claude retry" sense. Exit 2 feeds the stderr back to Claude as if the tool itself had returned it, and Claude will typically attempt a fix.
5. Notification -- when Claude wants the user's attention
Fires when Claude sends a notification to the user -- typically for permission requests, long-running task updates, or "I need you to look at this." Different from prompts: notifications are Claude-initiated, prompts are user-initiated.
Common uses: forward Claude notifications to Slack, escalate long-running task notifications to a phone push, mute noisy notifications during focus time.
Can block: no. Notifications are already emitted by the time the hook fires.
6. SubagentStop -- when a subagent finishes
Fires when a Task-tool subagent completes. This is the hook for cross-agent orchestration -- e.g., "when the researcher subagent finishes, kick off the reviewer subagent with its output."
Common uses: log subagent results to a workflow journal, gate subagent handoff on validation, aggregate multi-agent results.
Can block: yes -- but the block semantics are the same as Stop, not "swallow the result." Exit 2 prevents the subagent from stopping, forcing it to continue working (typically after your hook has told Claude why the current output is not good enough yet). Useful for validation gates where the subagent should retry until its output passes checks.
7. PreCompact -- before the context gets compacted
Fires when Claude is about to compact the conversation to free up context. This is the point where you can snapshot state that will be lost in the summary, or intervene in the compaction strategy.
Common uses: save the full transcript before compaction, refuse compaction if a critical operation is in flight, log which parts of the conversation are being summarized.
Can block: yes (this was updated recently -- the earlier docs said no). Exit 2 refuses the compaction, which is useful when you have long-horizon state that must not be summarized.
8. Stop -- when the session's turn ends
Fires at the end of every turn, after Claude has finished responding. This is the closing hook. Not to be confused with SessionEnd (which fires when the whole session terminates) -- Stop fires between turns.
Common uses: run a final linter pass on all modified files, commit the turn's changes, snapshot the working directory, notify Telegram that the turn is done.
Can block: yes -- exit 2 forces Claude to continue instead of stopping. This is how you build the "keep going until the tests pass" pattern.
The one asymmetry that trips everyone up
PreToolUse runs before the tool is called and can therefore block the tool from running at all. PostToolUse runs after the tool has already executed and cannot un-execute it. This sounds obvious.
The trap: PostToolUse looks like the natural place to run "did the tool do the right thing?" checks, because the tool has already done its work and you can inspect the results. In practice, if the tool wrote a file, that file is on disk by the time PostToolUse fires, regardless of what your hook does. Exit 2 does not delete the file -- it only communicates the error back to Claude so it can try again.
If you need to actually prevent an operation, hook it in PreToolUse with a matcher and validate the tool input. PostToolUse is for reactive checks (lint, format, index) that expect the operation happened, not for guard checks that need to prevent it.
The pattern that made hooks click for me
The single line I keep coming back to is from another chapter of the book this piece is drawn from: "Almost every time" (CLAUDE.md prose) is not the same as "every time" (a hook). The 8-hook lifecycle is the answer to where you turn "almost" into "every." You do not need to hook all 8 in one session. Most useful setups hook 2 or 3:
-
PreToolUseon Bash -- block destructive commands -
PostToolUseon Edit -- format and lint automatically -
Stop-- run the full test suite before letting the turn end
Three hooks. Roughly 40 lines of shell script. The delta in agent reliability is the difference between "I trust the agent on Fridays" and "I trust the agent on Fridays at 4pm with tests still failing."
The 20+ events not in the diagram
To close the loop: the 30+ events Anthropic documents are almost all specializations of the 8 above.
-
PostToolUseFailure=PostToolUsefor the error path -
SessionEnd= the outer bracket aroundSessionStart -
PermissionRequest,PermissionDenied= fine-grained variants ofNotification -
SubagentStart= the opening bracket aroundSubagentStop -
TaskCreated,TaskCompleted= task-tracking (TaskCreatetool) instrumentation, orthogonal to subagents -
WorktreeCreate,WorktreeRemove= Git-worktree-specific lifecycle events -
PreCompact,PostCompact= paired around the compaction step
They are useful when you need the specificity. The 8 core hooks are what you need for the diagram -- the mental model of when things happen and where to intervene. Once the diagram is in your head, the reference stops being overwhelming.
What to do Monday
If you have Claude Code installed and have never written a hook, the fastest way to internalize the lifecycle is:
-
Add a
PostToolUsehook that echoes to a log file for every tool call. Two lines of shell script. Run a Claude Code session and read the log. The sequence of tool invocations is the middle of the lifecycle made concrete. -
Add a
PreToolUsehook withmatcher: "Bash"that exits 2 on any command starting withrm -rf. This is the smallest useful safety hook and it is the first one everyone should have. -
Optionally: add a
Stophook that runs your test suite. If tests fail, exit 2 to force Claude to keep going. This is the "auto-repair" loop and it is the highest-leverage hook there is.
The rest of the 30 events can wait until you need them. The 8-hook lifecycle is what carries the mental model.
Book CTA
The full agent-lifecycle model -- the four feedback loops (immediate, task, session, strategic), the specific hook implementations for each, the CI-side enforcement that makes hooks work at team scale, and the harness patterns that let you compose them into a self-improving agent -- is written up in Harness Engineering Guide. Chapter 12 goes deep on hooks and lifecycle; chapter 11 covers the AGENTS.md / CLAUDE.md interaction with hooks; chapter 13 is the self-improving loop that hooks enable.
If you are specifically applying this to a Claude Code workflow, Claude Code Mastery chapter 11 covers the Skills + MCP + Hooks + Plugins decision -- when each is the right tool, and why picking the wrong one is expensive.

Top comments (0)