Claude Code hooks: which event to use when
Claude Code can run your own code at fixed points in a session: when it starts, before a prompt, before and after each tool call, and when Claude stops. These points are hook events.
This page sorts the events by when they fire and what they can do. It helps you pick the right one. Every fact comes from the official hooks reference.
What a hook is
A hook is a handler that Claude Code runs when an event fires. The handler can be a shell command, an HTTP endpoint, an MCP tool, a prompt to a Claude model, or a subagent.
Claude Code passes the event as JSON. A command reads it on stdin. An HTTP hook gets it as the body of a POST request.
You define hooks in a settings file, in three levels: the event, a matcher group that filters it, and the handlers to run. This example from the reference runs a lint script whenever Claude writes or edits a file:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "/path/to/lint-check.sh"
}
]
}
]
}
}Three rhythms
The reference names three rhythms:
- Once per session:
SessionStartandSessionEnd. - Once per turn:
UserPromptSubmit,StopandStopFailure. - On every tool call:
PreToolUseandPostToolUse.
The rhythm tells you the cost. A slow hook on every tool call slows down every step Claude takes. A slow SessionStart hook costs you once.
Six events to know first
SessionStart
- Fires
- When a session begins or resumes. The matcher tells how it started:
startup,resume,clear,compactorfork. - Can block
- No. Exit code 2 only shows your stderr to you.
- Output
- Plain text on stdout, or additionalContext. Claude gets it at the start of the conversation, before the first prompt.
- Use it for
- The current branch, open issues, a short briefing from earlier sessions.
Only command and mcp_tool handlers run here. Claude's first response waits for these hooks, so keep them fast.
UserPromptSubmit
- Fires
- When a prompt is submitted, before Claude processes it. Also on turns Claude Code starts by itself, such as a scheduled task or a
/loopiteration. It has no matcher. - Can block
- Yes. Exit code 2 or a block decision rejects the prompt. It never reaches Claude.
- Output
- Plain text on stdout, or additionalContext. Claude gets it alongside the prompt. The hook cannot replace the prompt.
- Use it for
- Notes that match this prompt. Checks on what a prompt may contain.
Command, HTTP and MCP tool handlers get 30 seconds by default. At the limit Claude Code discards the output, and the prompt goes on without it. Full guide with a working hook.
PreToolUse
- Fires
- After Claude creates the tool parameters, before the call runs. The matcher filters on the tool name, for example
Bash,Edit|Writeormcp__.*. - Can block
- Yes.
permissionDecisioncan beallow,deny,askordefer. Exit code 2 works like deny. - Output
updatedInputreplaces the tool's arguments.additionalContextlands next to the tool result.- Use it for
- Stop destructive commands. Keep edits out of folders that must stay untouched.
A command hook that times out does not block the call. Set "onFailure": "block" if it should. Files you attach with @ in a prompt reach Claude without a tool call, so this hook never sees them.
PostToolUse
- Fires
- Right after a tool call succeeds. A failed call fires
PostToolUseFailureinstead. - Can block
- No, the tool already ran. Exit code 2 shows your stderr to Claude. A block decision adds your reason next to the result.
- Output
updatedToolOutputreplaces the result Claude sees.additionalContextlands next to it.- Use it for
- Run a linter after each edit. Tell Claude a file is generated.
Stop
- Fires
- When Claude finishes responding. A user interrupt does not fire it. An API error fires StopFailure instead.
- Can block
- Yes: Claude keeps working. A block decision needs a
reason, which Claude reads as the reason to continue. - Output
additionalContextalso continues the turn, labelled as feedback instead of an error.- Use it for
- Check that the tests ran before Claude stops. Record a fix from the last answer.
The input carries last_assistant_message, Claude's final text, and stop_hook_active, which is true when a stop hook already continued the turn. After eight continuations in a row, Claude Code ends the turn anyway.
SessionEnd
- Fires
- When a session ends. The matcher tells why, for example
clear,logoutorother. - Can block
- No.
- Output
- Nothing for Claude. The session is over.
- Use it for
- Clean up, write logs, save state.
All SessionEnd hooks share a budget of 1.5 seconds. A longer timeout in your settings raises it, up to 60 seconds.
The other events
The reference lists more events for special cases. Here they are by topic, each with the moment it fires.
Tools and permissions
- PermissionRequest
- A tool call needs a permission decision. Decide with the decision object; exit code 2 has no effect here.
- PermissionDenied
- Auto mode denied a tool call. The hook can tell the model it may retry.
- PostToolUseFailure
- A tool call failed. Exit code 2 shows your stderr to Claude.
- PostToolBatch
- A batch of parallel tool calls resolved, before the next model call. Can stop the loop.
Prompts and turns
- UserPromptExpansion
- A command you typed expands into a prompt. Can block the expansion.
- StopFailure
- The turn ended on an API error, such as a rate limit. For logs and alerts; output is ignored.
- MessageDisplay
- Assistant text is displayed. Changes only what you see on screen.
Subagents, tasks and teams
- SubagentStart
- A subagent is spawned. Can add context for it.
- SubagentStop
- A subagent finishes. Can keep it working.
- TaskCreated
- A task is being created. Can roll it back.
- TaskCompleted
- A task is being marked as completed. Can prevent that.
- TeammateIdle
- An agent team teammate is about to go idle. Can keep it working.
Session, context and model
- Setup
- Claude Code starts with
--init-only, or with--initor--maintenancein-pmode. - InstructionsLoaded
- A CLAUDE.md or .claude/rules/*.md file is loaded into context.
- PreCompact
- Before context compaction. Can block it.
- PostCompact
- After compaction completes.
- PreModelSwitch
- Before a model switch you or a client requested. Can block it.
- PostModelSwitch
- After the session's model changed. Can add context.
Files, folders and settings
- CwdChanged
- The working directory changes, for example after a cd.
- DirectoryAdded
- A working directory is added during the session.
- FileChanged
- A watched file changes on disk, whatever wrote it.
- ConfigChange
- A configuration file changes during the session. Can block the change.
- WorktreeCreate
- A worktree is being created. Replaces the default git behavior.
- WorktreeRemove
- A worktree that a WorktreeCreate hook created is being removed.
Notifications and MCP
- Notification
- Claude Code sends a notification, for example when it needs your input.
- Elicitation
- An MCP server asks for user input during a tool call.
- ElicitationResult
- You answered an MCP request, before the answer goes back to the server.
How a hook answers
The exit code decides what happens next:
- 0: success. Claude Code reads the JSON you printed. On
SessionStart,UserPromptSubmit,UserPromptExpansionandPostModelSwitch, plain text on stdout becomes context for Claude. On most other events it goes to the debug log. - 2: a blocking error. On events that can block, Claude Code stops the action and uses your stderr as the reason.
- Any other code: a non-blocking error. The action goes ahead and you see a notice. This includes exit code 1, so a policy hook must exit with 2.
To add context, print one JSON object with hookSpecificOutput.additionalContext and nothing else on stdout. Where Claude reads it depends on the event:
- SessionStart, SubagentStart
- At the start of the conversation, before the first prompt.
- UserPromptSubmit, UserPromptExpansion
- Alongside the submitted prompt.
- PreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch
- Next to the tool result.
- Stop, SubagentStop
- At the end of the turn. The conversation continues so Claude can act on it.
- PostModelSwitch
- With the next request after the switch.
Claude Code wraps the text in a system reminder. Above 10,000 characters it saves the text to a file and passes Claude the path and a preview. Write facts, such as "This repo uses bun test". Text that reads like a system command can trigger Claude's prompt-injection defenses.
Command, HTTP and MCP tool handlers get 600 seconds by default. UserPromptSubmit, PreModelSwitch and PostModelSwitch get 30, MessageDisplay gets 10. Set timeout on the handler to change it.
Where hooks live
The file you put a hook in decides who runs it:
- ~/.claude/settings.json
- All your projects, on your machine only.
- .claude/settings.json
- One project. You can commit it, so your team runs it too.
- .claude/settings.local.json
- One project, for you alone.
- Managed policy settings
- The whole organization, set by an admin.
- Plugin hooks/hooks.json
- While the plugin is enabled.
- Skill frontmatter
- For the rest of the session, once the skill is invoked.
- Subagent frontmatter
- While that subagent runs.
Hooks from all levels add up. All hooks that match an event run in parallel. The same handler in two settings files runs once.
Before you add one
Command hooks run with your full user permissions. Read every hook before you add it, including the ones a repository brings along.
In an interactive session, Claude Code holds back hooks from settings files until you accept the workspace trust dialog for the folder. A -p or SDK session treats the folder as trusted. There, hooks committed in a repository's .claude/settings.json run right away. Before you script claude -p over a repository you did not write, check its .claude/ folder, or turn hooks off for that run:
claude -p "..." \
--settings '{"disableAllHooks": true}'To watch your hooks run, start Claude Code with claude --debug and read ~/.claude/debug/<session-id>.txt.
Hooks that carry experience
Each session starts without what the last one figured out. Three events let you carry that experience forward:
SessionStartbriefs the new session: what changed, what to watch out for.UserPromptSubmitadds the notes that match this prompt, right before Claude reads it.Stopreadslast_assistant_messageand can record a fix while it is fresh.
The notes only help when they are written well. How to write a lesson your coding agent can use shows the format with real examples.
cachly ships all three hooks. It turns your fixes, decisions and git history into lessons, and adds the matching ones to each prompt. Its servers are in the EU. What leaves your machine, and where it goes.
/plugin marketplace add cachly-dev/cachly-mcp
/plugin install cachly-brain@cachlySources
- Claude Code docs: Hooks reference (events, matchers, exit codes, context, timeouts, locations, trust)
- Claude Code docs: Automate actions with hooks
Checked against the hooks reference on .
More: UserPromptSubmit hook guide ยท Lessons learned for coding agents ยท All docs