How hooks work
A hook is a command (shell script, HTTP endpoint, or LLM prompt) bound to a specific event. When that event fires, Claude Code runs every matching hook and uses the exit code and output to decide what to do next. The input to each hook is a JSON object on stdin describing what happened — for example, the tool name and its arguments forPreToolUse, or the tool name and response for PostToolUse.
Exit code semantics
Exit code behavior varies by event. The full table is documented in each event’s description, but the general pattern is:| Exit code | Meaning |
|---|---|
0 | Success. Stdout may be shown to Claude (event-specific). |
2 | Block or inject. Show stderr to Claude and (for PreToolUse) prevent the tool call. |
| Other | Show stderr to the user only; execution continues. |
Hook events
PreToolUse — before tool execution
PreToolUse — before tool execution
- Exit
0: tool proceeds normally (stdout not shown) - Exit
2: block the tool call and show stderr to Claude so it can respond - Other: show stderr to the user but allow the tool call to continue
Bash, Write).PostToolUse — after tool execution
PostToolUse — after tool execution
inputs (the tool arguments) and response (the tool result).- Exit
0: stdout is shown in transcript mode (Ctrl+O) - Exit
2: show stderr to Claude immediately (Claude can respond) - Other: show stderr to the user only
PostToolUseFailure — after a tool error
PostToolUseFailure — after a tool error
tool_name, tool_input, error, error_type, is_interrupt, and is_timeout.Same exit code semantics as PostToolUse.Stop — before Claude concludes a response
Stop — before Claude concludes a response
- Exit
0: no output shown - Exit
2: show stderr to Claude and continue the conversation (Claude gets another turn) - Other: show stderr to the user only
SubagentStop — before a subagent concludes
SubagentStop — before a subagent concludes
Stop, but fires when a subagent (launched via the Agent tool) finishes. Input includes agent_id, agent_type, and agent_transcript_path. Same exit code semantics as Stop.SubagentStart — when a subagent starts
SubagentStart — when a subagent starts
agent_id and agent_type.- Exit
0: stdout is shown to the subagent’s initial prompt - Other: show stderr to user only
SessionStart — when a session begins
SessionStart — when a session begins
/clear, or /compact). Input contains the start source.- Exit
0: stdout is shown to Claude - Other: show stderr to user only (blocking errors are ignored)
source values: startup, resume, clear, compact.UserPromptSubmit — when you submit a prompt
UserPromptSubmit — when you submit a prompt
- Exit
0: stdout is shown to Claude (can prepend context) - Exit
2: block the prompt and show stderr to the user only - Other: show stderr to user only
PreCompact — before conversation compaction
PreCompact — before conversation compaction
- Exit
0: stdout is appended as custom compact instructions - Exit
2: block the compaction - Other: show stderr to user but proceed
trigger: manual or auto.PostCompact — after conversation compaction
PostCompact — after conversation compaction
- Exit
0: stdout shown to user - Other: show stderr to user only
Setup — repository setup and maintenance
Setup — repository setup and maintenance
trigger: init (project onboarding) or trigger: maintenance (periodic). Use this for one-time setup scripts or periodic maintenance tasks.- Exit
0: stdout shown to Claude - Other: show stderr to user only
PermissionRequest — when a permission dialog appears
PermissionRequest — when a permission dialog appears
hookSpecificOutput.decision to approve or deny programmatically.- Exit
0: use the hook’s decision if provided - Other: show stderr to user only
PermissionDenied — after auto mode denies a tool call
PermissionDenied — after auto mode denies a tool call
{"hookSpecificOutput":{"hookEventName":"PermissionDenied","retry":true}} to tell Claude it may retry.Notification — when notifications are sent
Notification — when notifications are sent
notification_type.- Exit
0: no output shown - Other: show stderr to user only
CwdChanged — after working directory changes
CwdChanged — after working directory changes
old_cwd and new_cwd. The CLAUDE_ENV_FILE environment variable is set — write bash export lines to that file to apply new env vars to subsequent Bash tool calls.FileChanged — when a watched file changes
FileChanged — when a watched file changes
matcher pattern changes on disk. The matcher specifies filename patterns to watch (e.g., .envrc|.env). Like CwdChanged, supports CLAUDE_ENV_FILE for injecting environment.SessionEnd — when a session ends
SessionEnd — when a session ends
reason: clear, logout, prompt_input_exit, or other.ConfigChange — when config files change
ConfigChange — when config files change
source: user_settings, project_settings, local_settings, policy_settings, or skills.- Exit
0: allow the change - Exit
2: block the change from being applied
InstructionsLoaded — when a CLAUDE.md file is loaded
InstructionsLoaded — when a CLAUDE.md file is loaded
WorktreeCreate / WorktreeRemove — worktree lifecycle
WorktreeCreate / WorktreeRemove — worktree lifecycle
WorktreeCreate fires when an isolated worktree needs to be created. Stdout should contain the absolute path of the created worktree. WorktreeRemove fires when a worktree should be cleaned up.Task events — task lifecycle
Task events — task lifecycle
TaskCreated and TaskCompleted fire when tasks are created or marked complete. Input includes task_id, task_subject, task_description, teammate_name, and team_name. Exit 2 prevents the state change.TeammateIdle — when a teammate is about to go idle
TeammateIdle — when a teammate is about to go idle
2 to send stderr to the teammate and prevent it from going idle.Elicitation / ElicitationResult — MCP elicitation
Elicitation / ElicitationResult — MCP elicitation
Elicitation fires when an MCP server requests user input. Return JSON in hookSpecificOutput to provide the response programmatically. ElicitationResult fires after the user responds, allowing you to modify or block the response.Configuring hooks
Run/hooks inside Claude Code to open the hooks configuration menu. The menu shows all configured hooks grouped by event and lets you add, edit, or remove them interactively.
Hooks are stored in the hooks field of settings files:
~/.claude/settings.json— user-level hooks (apply everywhere).claude/settings.json— project-level hooks (apply for this project).claude/settings.local.json— local hooks (not checked into VCS)
Configuration format
matcher(optional) — a string pattern to match against the event’s matchable field (for example,tool_nameforPreToolUse/PostToolUse,triggerforSetup,sourceforSessionStart)hooks— an array of hook commands to run when the matcher matches
Hook command types
- Shell command
- HTTP request
- LLM prompt
- Agent hook
command— the shell command to run (required)timeout— timeout in seconds (default: no limit)shell—"bash"(default) or"powershell"statusMessage— custom spinner text shown while the hook runsasync— run in background without blocking (true/false)once— run once and remove the hook automaticallyif— permission rule syntax to conditionally skip the hook (e.g.,"Bash(git *)")
Matcher patterns
For events that support matching (likePreToolUse, PostToolUse, SessionStart), the matcher field filters which inputs trigger the hook.
- An empty or absent
matchermatches all inputs for that event. - For tool events, the
matcheris matched against thetool_name(e.g.,"Bash","Write","Read"). - For
SessionStart, it matchessource(e.g.,"startup","compact"). - For
Setup, it matchestrigger(e.g.,"init","maintenance"). - For
FileChanged, thematcherspecifies filename patterns to watch (e.g.,".envrc|.env").
Example hooks
Auto-format files after editing
Run Prettier after every file write:Run tests after bash commands
Run the test suite after any bash command that touches source files:Log all tool usage
Append every tool call to a log file:Block dangerous commands
UsePreToolUse to prevent rm -rf from being called:
Inject environment on directory change
UseCwdChanged with CLAUDE_ENV_FILE to load .envrc when you change directories:
Hook timeout configuration
Set a per-hook timeout in seconds using thetimeout field:
timeout run until they exit naturally. For long-running hooks that should not block Claude, use "async": true.
Hooks vs. skills
| Feature | Hooks | Skills |
|---|---|---|
| When they run | Automatically on tool events | When Claude or you explicitly invoke /skill-name |
| Purpose | Side effects, gating, observability | On-demand workflows and capabilities |
| Configuration | Settings JSON | Markdown files in .claude/skills/ |
| Input | JSON from the tool event | The arguments you pass to the skill |