Claude Code hooks

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:

.claude/settings.json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/lint-check.sh"
          }
        ]
      }
    ]
  }
}

Three rhythms

The reference names three rhythms:

  • Once per session: SessionStart and SessionEnd.
  • Once per turn: UserPromptSubmit, Stop and StopFailure.
  • On every tool call: PreToolUse and PostToolUse.

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, compact or fork.
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 /loop iteration. 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|Write or mcp__.*.
Can block
Yes. permissionDecision can be allow, deny, ask or defer. Exit code 2 works like deny.
Output
updatedInput replaces the tool's arguments. additionalContext lands 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 PostToolUseFailure instead.
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
updatedToolOutput replaces the result Claude sees. additionalContext lands 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
additionalContext also 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, logout or other.
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 --init or --maintenance in -p mode.
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, UserPromptExpansion and PostModelSwitch, 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:

terminal
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:

  1. SessionStart briefs the new session: what changed, what to watch out for.
  2. UserPromptSubmit adds the notes that match this prompt, right before Claude reads it.
  3. Stop reads last_assistant_message and 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.

Claude Code
/plugin marketplace add cachly-dev/cachly-mcp
/plugin install cachly-brain@cachly

Sources

Checked against the hooks reference on .

More: UserPromptSubmit hook guide ยท Lessons learned for coding agents ยท All docs