Skip to content
Code Guide

Hooks Events Reference

Complete reference for all 30 Claude Code hook events: matcher fields, input schemas, decision control, and timeout defaults. Source: official Anthropic documentation.

For the audit skill, see examples/skills/eval-hooks/SKILL.md.


EventFires whenMatcher fieldCan block?Default timeout
SessionStartSession begins or resumessourceNo600s
Setup--init-only or -p --init/--maintenancetriggerNo600s
UserPromptSubmitUser submits a promptnoneYes30s
UserPromptExpansionSlash command expands to promptcommand_nameYes600s
PreToolUseBefore tool call executestool_nameYes600s
PermissionRequestPermission dialog is about to appeartool_nameYes (via JSON)600s
PermissionDeniedAuto-mode classifier denies a calltool_nameNo (retry only)600s
PostToolUseAfter tool call succeedstool_nameNo (stderr to Claude)600s
PostToolUseFailureAfter tool call failstool_nameNo600s
PostToolBatchAfter full parallel batch resolvesnoneYes (stops loop)600s
NotificationClaude sends a notificationnotification_typeNo600s
MessageDisplayAssistant message text streamsnoneNo10s
SubagentStartSubagent spawned via Agent toolagent_typeNo600s
SubagentStopSubagent finishesagent_typeYes600s
TaskCreatedTask being created via TaskCreatenoneYes600s
TaskCompletedTask being marked as completednoneYes600s
StopClaude finishes respondingnoneYes (continues turn)600s
StopFailureTurn ends due to API errorerrorNo (ignored)600s
TeammateIdleAgent team teammate goes idlenoneYes600s
InstructionsLoadedCLAUDE.md or rules file loadedload_reasonNo600s
ConfigChangeConfig file changes during sessionsourceYes (not policy_settings)600s
CwdChangedWorking directory changesnoneNo600s
FileChangedWatched file changes on diskfilename (literal)No600s
WorktreeCreateWorktree being creatednoneYes (any non-zero)600s
WorktreeRemoveWorktree being removednoneNo600s
PreCompactBefore context compactiontriggerYes600s
PostCompactAfter compaction completestriggerNo600s
ElicitationMCP server requests user inputmcp_server_nameYes600s
ElicitationResultUser responds to MCP elicitationmcp_server_nameYes600s
SessionEndSession terminatesreasonNo1.5s budget

Timeout exceptions: prompt hooks default to 30s. agent hooks default to 60s. SessionEnd has a 1.5s total budget; set explicit timeout on individual hooks to raise it (max 60s). Plugin hooks do not raise the budget.


The matcher field filters on a different field depending on the event type.

Events: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied

Values: Bash, Edit, Write, Read, Glob, Grep, Agent, WebFetch, WebSearch, AskUserQuestion, ExitPlanMode, and MCP tools as mcp__<server>__<tool>.

Matching rules:

  • Only letters/digits/underscores/pipe: exact string or pipe-separated list (Edit|Write)
  • Contains any other character: treated as JS regex (mcp__memory__.*)
  • "*", "", or absent: matches all

To match every tool from an MCP server: mcp__memory__.* (the .* is required; mcp__memory without it is an exact string and matches no tool).

ValueWhen
startupNew session
resume--resume, --continue, or /resume
clear/clear
compactAuto or manual compaction
ValueWhen
initclaude --init-only or claude -p --init
maintenanceclaude -p --maintenance
ValueWhen
clear/clear command
resumeInteractive /resume switch
logoutUser logged out
prompt_input_exitExited while prompt input was visible
bypass_permissions_disabledBypass mode disabled
otherOther exit reasons

Notification: matcher filters on notification_type

Section titled “Notification: matcher filters on notification_type”

Values: permission_prompt, idle_prompt, auth_success, elicitation_dialog, elicitation_complete, elicitation_response

SubagentStart / SubagentStop: matcher filters on agent_type

Section titled “SubagentStart / SubagentStop: matcher filters on agent_type”

Values: general-purpose, Explore, Plan, or custom agent names (the name field from the agent’s frontmatter, not the filename).

PreCompact / PostCompact: matcher filters on trigger

Section titled “PreCompact / PostCompact: matcher filters on trigger”

Values: manual (from /compact), auto (automatic)

InstructionsLoaded: matcher filters on load_reason

Section titled “InstructionsLoaded: matcher filters on load_reason”

Values: session_start, nested_traversal, path_glob_match, include, compact

Values: user_settings, project_settings, local_settings, policy_settings, skills

Values: rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, unknown

UserPromptExpansion: matcher filters on command_name

Section titled “UserPromptExpansion: matcher filters on command_name”

Your skill or command name as typed by the user (without the leading /).

Elicitation / ElicitationResult: matcher filters on mcp_server_name

Section titled “Elicitation / ElicitationResult: matcher filters on mcp_server_name”

Your configured MCP server name.

Split on |, each segment is registered as a literal filename watched in the current directory. Example: ".envrc|.env". Unlike other events, regex patterns are not applied here. The same value builds the watch list AND filters which hooks run.

UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, CwdChanged, MessageDisplay

Adding a matcher field to these events is silently ignored.


Only exit code 2 blocks. Exit code 1 is non-blocking: the action proceeds and the first line of stderr appears in the transcript. WorktreeCreate is the exception: any non-zero code fails creation.

EventWhat happens on exit 2
PreToolUseBlocks the tool call; stderr fed to Claude
PermissionRequestDenies the permission
UserPromptSubmitBlocks prompt and erases it from context
UserPromptExpansionBlocks the expansion
StopPrevents stopping; continues the turn
SubagentStopPrevents subagent from stopping
TeammateIdleTeammate continues working; stderr fed back
TaskCreatedRolls back task creation; stderr fed back
TaskCompletedPrevents completion; stderr fed back
ConfigChangeBlocks config change (not policy_settings)
PostToolBatchStops agentic loop before next model call
PreCompactBlocks compaction
ElicitationDenies the elicitation
ElicitationResultBlocks response (effective action becomes decline)
WorktreeCreateAny non-zero exit code fails creation
PostToolUseShows stderr to Claude (tool already ran)
PostToolUseFailureShows stderr to Claude
StopFailureIgnored entirely (output and exit code ignored)
SessionEndShows stderr to user only
SessionStart, Setup, SubagentStartShows stderr to user only
NotificationShows stderr to user only
InstructionsLoadedExit code ignored
PermissionDeniedExit code and stderr ignored
CwdChanged, FileChanged, WorktreeRemoveLogged in debug mode only
PostCompactShows stderr to user only
MessageDisplayOriginal text displayed unchanged

Used by: UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact

{ "decision": "block", "reason": "Explanation" }

Only "block" is valid. Omit decision to allow. For Stop and SubagentStop, the reason becomes Claude’s next instruction.

Uses hookSpecificOutput for richer control. Precedence when hooks conflict: deny > defer > ask > allow.

{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow | deny | ask | defer",
"permissionDecisionReason": "Shown to user (allow/ask) or to Claude (deny)",
"updatedInput": { "field": "new value" },
"additionalContext": "Context injected next to tool result"
}
}

defer only works in -p (non-interactive) mode. Process exits with stop_reason: "tool_deferred" and the calling process can resume later. defer does not work when Claude makes several tool calls at once.

{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow | deny",
"updatedInput": {},
"updatedPermissions": [],
"message": "Reason for deny",
"interrupt": false
}
}
}

Exit code and stderr are ignored. To signal retry:

{
"hookSpecificOutput": {
"hookEventName": "PermissionDenied",
"retry": true
}
}
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Context injected next to tool result",
"updatedToolOutput": { "stdout": "...", "stderr": "", "interrupted": false, "isImage": false }
}
}

updatedToolOutput changes what Claude sees, not what already executed. Must match the tool’s output shape exactly. Built-in tools with incorrect shapes fall back to the original.

Command hooks print the absolute path on stdout. HTTP hooks return:

{ "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/abs/path" } }

Any failure or missing path fails worktree creation.

{
"hookSpecificOutput": {
"hookEventName": "MessageDisplay",
"displayContent": "Replacement text shown on screen"
}
}

Changes only what appears on screen. Claude and the transcript keep the original.

{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Context before first prompt",
"sessionTitle": "branch-or-feature-name",
"watchPaths": ["/absolute/path/to/watch"],
"reloadSkills": true,
"initialUserMessage": "First turn in -p mode"
}
}

reloadSkills: true rescans skill directories after hooks complete, so skills installed by the hook are available immediately in the same session.

{
"hookSpecificOutput": {
"hookEventName": "Elicitation",
"action": "accept | decline | cancel",
"content": { "field_name": "value" }
}
}

Same format as Elicitation output. Overrides what the user submitted.

{
"continue": false,
"stopReason": "Message shown to user (not Claude)",
"suppressOutput": true,
"systemMessage": "Warning shown to user",
"terminalSequence": "\033]777;notify;Title;Body\007"
}

continue: false stops Claude entirely, takes precedence over all decision fields.

terminalSequence: desktop notifications and window titles via OSC sequences. Restricted to OSC 0/1/2/9/99/777 and BEL. Writing to /dev/tty directly fails since v2.1.139 because hooks run without a controlling terminal.


All events receive: session_id, transcript_path, cwd, hook_event_name, and usually permission_mode. Subagent hooks also receive agent_id and agent_type.

EventEvent-specific extra input fields
SessionStartsource, model, optionally agent_type, session_title
Setuptrigger ("init" or "maintenance")
UserPromptSubmitprompt
UserPromptExpansionexpansion_type, command_name, command_args, command_source, prompt
PreToolUsetool_name, tool_input, tool_use_id
PermissionRequesttool_name, tool_input, permission_suggestions
PermissionDeniedtool_name, tool_input, tool_use_id, reason
PostToolUsetool_name, tool_input, tool_response, tool_use_id, duration_ms
PostToolUseFailuretool_name, tool_input, tool_use_id, error, is_interrupt, duration_ms
PostToolBatchtool_calls (array with tool_name, tool_input, tool_use_id, tool_response)
Notificationmessage, title, notification_type
MessageDisplayturn_id, message_id, index, final, delta
SubagentStartagent_id, agent_type
SubagentStopstop_hook_active, agent_id, agent_type, agent_transcript_path, last_assistant_message
TaskCreatedtask_id, task_subject, task_description, teammate_name, team_name
TaskCompletedtask_id, task_subject, task_description, teammate_name, team_name
Stopstop_hook_active, last_assistant_message, background_tasks, session_crons
StopFailureerror, error_details, last_assistant_message
TeammateIdleteammate_name, team_name
InstructionsLoadedfile_path, memory_type, load_reason, globs, trigger_file_path, parent_file_path
ConfigChangesource, file_path
CwdChangedold_cwd, new_cwd
FileChangedfile_path, event ("change", "add", "unlink")
WorktreeCreatename (slug for the new worktree)
WorktreeRemoveworktree_path
PreCompacttrigger, custom_instructions
PostCompacttrigger, compact_summary
Elicitationmcp_server_name, message, mode, url, elicitation_id, requested_schema
ElicitationResultmcp_server_name, action, mode, elicitation_id, content
SessionEndreason

FieldDescription
typecommand, http, mcp_tool, prompt, or agent
ifPermission rule syntax to narrow the handler. Tool events only (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied). On any other event, a hook with if set never runs
timeoutSeconds before canceling
statusMessageCustom spinner message while hook runs
onceRun once per session then remove. Honored only in skill frontmatter, ignored in settings files
FieldDescription
commandShell command or executable path
argsArgument vector: triggers exec form (no shell involved)
asynctrue to run in background without blocking
asyncRewakeLike async: true but wakes Claude on exit 2, showing stderr (or stdout if stderr is empty) as a system reminder
shell"bash" (default) or "powershell" (Windows)

Shell form (args absent): command passed to sh -c. Supports pipes, &&, globs. Exec form (args present): command is the executable, each args element is one verbatim argument. Use for paths with spaces or when referencing ${CLAUDE_PROJECT_DIR}.

FieldDescription
urlPOST endpoint URL
headersAdditional headers (supports $VAR interpolation)
allowedEnvVarsEnv vars allowed to be interpolated in header values

Non-2xx responses are non-blocking. To block, return 2xx with decision: "block" in the JSON body.

FieldDescription
serverConnected MCP server name
toolTool name on that server
inputTool arguments. String values support ${path} substitution from hook input

Server must already be connected. SessionStart and Setup typically fire before servers finish connecting.

FieldDescription
promptPrompt text. Use $ARGUMENTS for the hook’s JSON input
modelModel override (default: fast model, typically Haiku)
continueOnBlockWhen ok: false, feed the reason back to Claude and continue instead of stopping

Returns { "ok": true/false, "reason": "..." }. Supports the same events as command hooks except SessionStart and Setup.

FieldDescription
promptTask description. Use $ARGUMENTS for the hook’s JSON input
modelModel override

Spawns a subagent that can use Read, Grep, Glob (up to 50 turns), then returns the same { "ok": true/false } schema. Experimental.


PlaceholderResolves to
${CLAUDE_PROJECT_DIR}Project root directory
${CLAUDE_PLUGIN_ROOT}Plugin installation directory (changes on update)
${CLAUDE_PLUGIN_DATA}Plugin persistent data directory (survives updates)

Prefer exec form for hooks referencing these: each args element is passed verbatim, no quoting needed for spaces or special chars.


Available in SessionStart, Setup, CwdChanged, and FileChanged hooks. Write export VAR=value lines to this path to persist variables into subsequent Bash commands for the session.

Terminal window
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
fi

Use append (>>) to preserve variables set by other hooks.


Stop hook 8-block cap: Claude Code overrides Stop hooks after 8 consecutive blocks. Read stop_hook_active from stdin and exit 0 when it is true to let Claude stop cleanly.

Exit 1 does not block: Only exit 2 blocks a PreToolUse call or UserPromptSubmit. Exit 1 is non-blocking: the action proceeds. This surprises most developers coming from Unix conventions.

asyncRewake vs async: asyncRewake: true runs the hook in the background AND wakes the session when the process exits with code 2, even if Claude is idle. Use when a long-running background check needs to report a failure mid-session.

SessionEnd budget: Total budget is 1.5s. Setting timeout: 30 on a hook raises the budget to 30s for the whole group. Plugin hooks do not contribute to the budget calculation. Override with CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000.

MessageDisplay batching: Fires multiple times per message (once per batch of lines) in interactive mode, once after the full message in -p/Agent SDK mode. final: true marks the last batch; don’t rely on a non-empty delta as the end signal.

WorktreeCreate replaces git entirely: The hook must handle the full worktree setup. .worktreeinclude is not processed. Copy .env and other local files inside the hook.

if field on non-tool events: Adding if to SessionStart, Stop, or any non-tool event silently prevents the hook from running at all.

Multiple PreToolUse hooks with updatedInput: Hooks run in parallel; the last to finish wins. Order is non-deterministic. Avoid having two hooks on the same matcher both returning updatedInput.

PermissionRequest does not prevent via exit 2: Use hookSpecificOutput.decision.behavior: "deny" in JSON output. Exit 2 is not the mechanism here.

Prompt hooks on PermissionDenied: Output is discarded. The only field this event reads is hookSpecificOutput.retry, which prompt and agent hooks cannot set. Use a command hook for retry signals.

Hooks without a controlling terminal: Since v2.1.139, hooks run without /dev/tty. Use terminalSequence in JSON output to emit desktop notifications or window titles instead of writing escape sequences directly.

UserPromptSubmit default timeout: 30s, not 600s. A stuck hook here blocks all user input. Set an explicit timeout if you need more time.