Skip to main content
Hooks are shell commands, HTTP endpoints, LLM prompts, or in-process callbacks that fire at defined points in Claude’s agentic loop. They let you inject logic before or after tool calls, intercept permission requests, react to session lifecycle events, and more.

Configuration

Hooks are configured in any Claude Code settings file. The top-level hooks key maps event names to an array of matcher objects.
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'About to run bash command' >&2"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "/usr/local/bin/lint-changed-file '$TOOL_INPUT'"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "notify-send 'Claude finished'"
          }
        ]
      }
    ]
  }
}

Settings file locations

ScopePathPriority
User~/.claude/settings.jsonLow
Project.claude/settings.jsonMedium
Local.claude/settings.local.jsonHigh
Higher-priority settings files take precedence. All hooks across all files run; they are not overridden.

Matcher configuration

matcher
string
A string pattern that filters when these hooks run. The field matched depends on the event:
  • PreToolUse / PostToolUse / PostToolUseFailure / PermissionRequest / PermissionDenied — matched against tool_name
  • Notification — matched against notification_type
  • SessionStart — matched against source (startup, resume, clear, compact)
  • Setup — matched against trigger (init, maintenance)
  • SubagentStart / SubagentStop — matched against agent_type
  • PreCompact / PostCompact — matched against trigger (manual, auto)
  • StopFailure — matched against error
  • ConfigChange — matched against source
  • InstructionsLoaded — matched against load_reason
  • Elicitation / ElicitationResult — matched against mcp_server_name
  • FileChanged — matched against filenames (e.g., ".envrc|.env")
Omit matcher to run the hook for all instances of the event.
hooks
HookCommand[]
required
One or more hook definitions to execute when the matcher fires.

Hook types

command — shell command

{
  "type": "command",
  "command": "jq '.tool_name' && my-validator",
  "timeout": 30,
  "shell": "bash",
  "async": false,
  "once": false,
  "if": "Bash(git *)",
  "statusMessage": "Validating command..."
}
The hook input JSON is piped to the command’s stdin. The CLAUDE_ENV_FILE environment variable is set for CwdChanged and FileChanged hooks — write export KEY=value lines there to inject environment variables into subsequent Bash tool calls.
command
string
required
Shell command to execute.
timeout
number
Timeout in seconds. Defaults to the global hook timeout (60 s).
shell
'bash' | 'powershell'
Shell interpreter. bash uses your $SHELL (bash/zsh/sh). Defaults to bash.
async
boolean
When true, the hook runs in the background without blocking Claude. Output is ignored.
asyncRewake
boolean
When true, the hook runs in the background but wakes the model if it exits with code 2. Implies async: true.
once
boolean
When true, the hook runs once and is removed from the configuration after execution.
if
string
Permission rule syntax (e.g., "Bash(git *)") evaluated against the hook input. The hook is skipped if the condition does not match. Avoids spawning processes for non-matching tool calls.
statusMessage
string
Custom message shown in the spinner while the hook runs.

prompt — LLM evaluation

{
  "type": "prompt",
  "prompt": "Check whether this bash command is safe: $ARGUMENTS",
  "model": "claude-haiku-4-5",
  "timeout": 30
}
The $ARGUMENTS placeholder is replaced with the hook input JSON. The model’s response is treated as the hook output.
prompt
string
required
Prompt sent to the model. Use $ARGUMENTS to embed the hook input.
model
string
Model to use. Defaults to the small fast model.

agent — agentic verifier

{
  "type": "agent",
  "prompt": "Verify that unit tests ran and passed.",
  "model": "claude-haiku-4-5",
  "timeout": 120
}
Runs a short agentic loop that can call tools to verify the action. Use for PostToolUse hooks where you want the verifier to read files or run commands.
prompt
string
required
Verification prompt. Use $ARGUMENTS to embed the hook input.

http — HTTP endpoint

{
  "type": "http",
  "url": "https://my-server.example.com/hook",
  "headers": {
    "Authorization": "Bearer $MY_TOKEN"
  },
  "allowedEnvVars": ["MY_TOKEN"],
  "timeout": 10
}
POSTs the hook input JSON to the given URL. Header values can reference environment variables using $VAR_NAME syntax, but only variables listed in allowedEnvVars are interpolated.
url
string
required
URL to POST the hook input JSON to.
headers
Record<string, string>
Additional request headers.
allowedEnvVars
string[]
Environment variable names that may be interpolated in header values.

Base hook input

Every hook receives a JSON object on stdin with these fields present for all event types.
hook_event_name
string
required
The event that fired (e.g., "PreToolUse").
session_id
string
required
Current session identifier.
transcript_path
string
required
Absolute path to the JSONL transcript file for this session.
cwd
string
required
Current working directory at the time the hook fired.
permission_mode
string
Active permission mode ("default", "acceptEdits", "bypassPermissions", "plan", "dontAsk").
agent_id
string
Subagent identifier. Present only when the hook fires from within a subagent. Use this field to distinguish subagent calls from main-thread calls.
agent_type
string
Agent type name (e.g., "general-purpose", "code-reviewer"). Present when the hook fires from a subagent, or on the main thread of a session started with --agent.

Sync hook output (JSON on stdout)

For blocking hooks, write a JSON object to stdout before exiting. The schema is the same for all events, with event-specific fields nested under hookSpecificOutput.
{
  "continue": true,
  "suppressOutput": false,
  "decision": "approve",
  "reason": "Command looks safe",
  "systemMessage": "The hook approved this action.",
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "additionalContext": "Verified by security scanner."
  }
}
continue
boolean
When false, Claude stops the current turn immediately.
suppressOutput
boolean
When true, the hook’s stdout is not shown in transcript mode.
decision
'approve' | 'block'
Explicit approve/block decision. Takes effect only when the CLI reads it.
systemMessage
string
Message injected into Claude’s context as a system turn.
reason
string
Human-readable reason for the decision. Shown to the user when a hook blocks an action.
hookSpecificOutput
object
Event-specific output. See each event section below for the allowed fields.

Hook events

PreToolUse

Fires immediately before a tool executes. You can inspect the tool input, approve or block the call, or modify the input before it reaches the tool. When it fires: Before every tool invocation. Input fields:
tool_name
string
Name of the tool about to run (e.g., "Bash", "Write", "mcp__myserver__my_tool").
tool_input
unknown
The raw tool input object as Claude submitted it.
tool_use_id
string
Unique ID for this tool invocation.
Exit codes:
Exit codeEffect
0Stdout/stderr not shown. Hook output JSON applied if valid.
2Stderr shown to Claude; tool call is blocked.
OtherStderr shown to user only; tool call continues.
hookSpecificOutput fields:
hookEventName
literal: 'PreToolUse'
required
Must be "PreToolUse".
permissionDecision
'allow' | 'deny' | 'ask'
Override the permission decision for this tool call. "allow" approves the call; "deny" blocks it; "ask" forces the permission dialog.
permissionDecisionReason
string
Reason string shown to the user when the decision is "deny" or "ask".
updatedInput
Record<string, unknown>
Replacement tool input. When provided, the tool receives this object instead of Claude’s original input.
additionalContext
string
Text injected into Claude’s context for this turn.

PostToolUse

Fires after a tool completes successfully. You can observe the tool output or inject context for Claude to act on. When it fires: After every successful tool execution. Input fields:
tool_name
string
Tool that ran.
tool_input
unknown
Input that was passed to the tool.
tool_response
unknown
The tool’s output.
tool_use_id
string
Unique ID for this invocation.
Exit codes:
Exit codeEffect
0Stdout shown in transcript mode (Ctrl+O).
2Stderr shown to Claude immediately as a system message.
OtherStderr shown to user only.
hookSpecificOutput fields:
additionalContext
string
Context injected into Claude’s conversation after the tool result.
updatedMCPToolOutput
unknown
Replacement for the MCP tool’s output. Only effective for MCP tool calls.

PostToolUseFailure

Fires when a tool call ends in an error or is interrupted. When it fires: When a tool throws or is aborted. Input fields:
tool_name
string
Tool that failed.
tool_input
unknown
Input that was passed to the tool.
tool_use_id
string
Unique ID for this invocation.
error
string
Error message from the tool.
is_interrupt
boolean
Whether the failure was caused by an interrupt signal.
Exit codes: Same as PostToolUse. Hook output and exit codes are logged but do not affect the failed tool result.

PermissionRequest

Fires when a permission dialog would be shown to the user. Hooks can programmatically approve or deny without showing any UI. When it fires: When Claude requests permission for a tool call and the default behavior is to prompt. Input fields:
tool_name
string
Tool requesting permission.
tool_input
unknown
Input the tool would receive if approved.
permission_suggestions
PermissionUpdate[]
Suggested permission rules (allow/deny) that the UI would offer.
Exit codes:
Exit codeEffect
0Hook decision applied if hookSpecificOutput.decision is set; otherwise falls through to normal dialog.
OtherStderr shown to user; falls through to normal dialog.
hookSpecificOutput fields:
hookEventName
literal: 'PermissionRequest'
required
Must be "PermissionRequest".
decision
object
The approval or denial decision.

PermissionDenied

Fires when a tool call is denied (by rules, mode, or classifier). You can instruct Claude to retry the action. When it fires: After every permission denial. Input fields:
tool_name
string
Denied tool.
tool_input
unknown
Input that was denied.
tool_use_id
string
Unique ID for this invocation.
reason
string
Human-readable denial reason.
Exit codes:
Exit codeEffect
0Stdout shown in transcript mode.
OtherStderr shown to user only.
hookSpecificOutput fields:
retry
boolean
When true, Claude is told it may retry the denied action.

Stop

Fires just before Claude concludes its response for the current turn. When it fires: When the model is about to stop and return control to the user. Input fields:
stop_hook_active
boolean
Whether a Stop hook is currently running (prevents infinite loops if your Stop hook itself would trigger a Stop).
last_assistant_message
string
Text content of the last assistant message before stopping. Saves you from parsing the transcript file.
Exit codes:
Exit codeEffect
0Stdout/stderr not shown.
2Stderr injected as a system message; Claude continues the conversation.
OtherStderr shown to user only; Claude stops.
Use exit code 2 from a Stop hook to check Claude’s output and keep the conversation going if a condition is unmet — for example, if tests are still failing.

StopFailure

Fires instead of Stop when the turn ends due to an API error. When it fires: When a rate limit, authentication failure, or other API error ends the turn. Input fields:
error
'authentication_failed' | 'billing_error' | 'rate_limit' | 'invalid_request' | 'server_error' | 'unknown' | 'max_output_tokens'
The error category.
error_details
string
Detailed error message.
last_assistant_message
string
Last assistant message text, if any was produced before the error.
Behavior: Fire-and-forget. Hook output and exit codes are ignored.

SubagentStart

Fires when Claude spawns a subagent via the Agent tool. When it fires: When an Agent tool call begins. Input fields:
agent_id
string
Unique ID for this subagent instance.
agent_type
string
Agent type name (e.g., "general-purpose").
Exit codes:
Exit codeEffect
0Stdout shown to the subagent as context.
OtherStderr shown to user only.
hookSpecificOutput fields:
additionalContext
string
Context injected into the subagent’s conversation at the start.

SubagentStop

Fires just before a subagent concludes its response. Mirrors Stop but for subagents. When it fires: When a subagent is about to return its result to the parent. Input fields:
agent_id
string
Subagent instance ID.
agent_type
string
Agent type name.
agent_transcript_path
string
Path to the subagent’s JSONL transcript.
stop_hook_active
boolean
Whether a SubagentStop hook is already running.
last_assistant_message
string
Last message from the subagent.
Exit codes:
Exit codeEffect
0Stdout/stderr not shown.
2Stderr shown to subagent; subagent continues running.
OtherStderr shown to user only; subagent stops.

SessionStart

Fires when a session begins. Use this to inject initial context or set up the environment. When it fires: On session startup, resume, clear (/clear), or after compaction. Input fields:
source
'startup' | 'resume' | 'clear' | 'compact'
What triggered the session start.
model
string
Active model for the session.
Exit codes:
Exit codeEffect
0Stdout shown to Claude as initial context.
OtherStderr shown to user only.
hookSpecificOutput fields:
additionalContext
string
Context injected into Claude’s system prompt for this session.
initialUserMessage
string
Auto-submitted as the first user message of the session.
watchPaths
string[]
Absolute file paths to register with the FileChanged watcher.

SessionEnd

Fires when a session is about to end. When it fires: On clear, logout, prompt-input exit, or other termination reasons. Input fields:
reason
'clear' | 'resume' | 'logout' | 'prompt_input_exit' | 'other' | 'bypass_permissions_disabled'
The reason the session is ending.
Exit codes: Exit code 0 completes successfully. Other exit codes show stderr to the user.

Setup

Fires during repository initialization and maintenance checks. When it fires: On init (first time Claude Code runs in a directory) or maintenance (periodic checks). Input fields:
trigger
'init' | 'maintenance'
What triggered the setup hook.
Exit codes:
Exit codeEffect
0Stdout shown to Claude.
OtherStderr shown to user only. Blocking errors are ignored.
hookSpecificOutput fields:
additionalContext
string
Context provided to Claude for the setup phase.

PreCompact

Fires before context compaction begins. When it fires: Before the compaction summary is generated, whether triggered manually (/compact) or automatically. Input fields:
trigger
'manual' | 'auto'
Whether compaction was requested by the user or triggered automatically.
custom_instructions
string | null
Any custom compaction instructions already configured.
Exit codes:
Exit codeEffect
0Stdout appended as custom compaction instructions.
2Compaction is blocked.
OtherStderr shown to user; compaction continues.

PostCompact

Fires after compaction completes. When it fires: After the compaction summary has been generated and applied. Input fields:
trigger
'manual' | 'auto'
How compaction was triggered.
compact_summary
string
The summary produced by compaction.
Exit codes: Exit code 0 shows stdout to the user. Other exit codes show stderr to the user.

UserPromptSubmit

Fires when the user submits a prompt, before Claude processes it. When it fires: Each time you press Enter with a message in the terminal. Input fields:
prompt
string
The raw prompt text the user submitted.
Exit codes:
Exit codeEffect
0Stdout shown to Claude as additional context.
2Processing blocked; original prompt erased; stderr shown to user.
OtherStderr shown to user only.
hookSpecificOutput fields:
additionalContext
string
Context appended to Claude’s view of the user message.

Notification

Fires when Claude Code sends a notification (e.g., permission prompts, idle alerts). When it fires: When a notification event is raised internally. Input fields:
message
string
Notification message text.
title
string
Notification title.
notification_type
'permission_prompt' | 'idle_prompt' | 'auth_success' | 'elicitation_dialog' | 'elicitation_complete' | 'elicitation_response'
The type of notification.
Exit codes: Exit code 0 produces no output. Other exit codes show stderr to the user.

Elicitation

Fires when an MCP server requests user input. Hooks can auto-respond without showing the dialog. When it fires: When an MCP server sends an elicitation request (a structured input form or URL). Input fields:
mcp_server_name
string
Name of the MCP server requesting input.
message
string
The prompt message from the server.
mode
'form' | 'url'
Input mode.
elicitation_id
string
Request identifier.
requested_schema
Record<string, unknown>
JSON schema describing the expected input structure.
Exit codes:
Exit codeEffect
0Use hook response if hookSpecificOutput is provided; otherwise show dialog.
2Deny the elicitation.
OtherStderr shown to user only.
hookSpecificOutput fields:
action
'accept' | 'decline' | 'cancel'
The programmatic response to the elicitation.
content
Record<string, unknown>
Form data to submit when action is "accept".

ElicitationResult

Fires after a user responds to an MCP elicitation. Hooks can observe or override the response. When it fires: After the user (or an Elicitation hook) responds to the elicitation. Input fields:
mcp_server_name
string
Name of the MCP server.
elicitation_id
string
Request identifier.
action
'accept' | 'decline' | 'cancel'
How the user responded.
content
Record<string, unknown>
Submitted form data, if accepted.
hookSpecificOutput fields:
action
'accept' | 'decline' | 'cancel'
Override the user’s action before it is sent to the server.
content
Record<string, unknown>
Override the submitted content.

ConfigChange

Fires when a settings file changes during a session. When it fires: When user_settings, project_settings, local_settings, policy_settings, or skills files are modified on disk. Input fields:
source
'user_settings' | 'project_settings' | 'local_settings' | 'policy_settings' | 'skills'
Which settings source changed.
file_path
string
Absolute path to the changed file.
Exit codes:
Exit codeEffect
0Allow the change to be applied.
2Block the change from being applied to the session.
OtherStderr shown to user only.

InstructionsLoaded

Fires when a CLAUDE.md or instruction rule file is loaded. This event is observability-only. When it fires: When any instruction file is loaded into context. Input fields:
file_path
string
Path to the loaded file.
memory_type
'User' | 'Project' | 'Local' | 'Managed'
The memory scope of the file.
load_reason
'session_start' | 'nested_traversal' | 'path_glob_match' | 'include' | 'compact'
Why the file was loaded.
globs
string[]
The paths: frontmatter patterns that matched (if load_reason is path_glob_match).
trigger_file_path
string
The file Claude accessed that caused this load (for path_glob_match).
parent_file_path
string
The file that @included this one (for include).
Behavior: Blocking not supported. Exit code 0 completes normally. Other exit codes show stderr to the user.

WorktreeCreate

Fires when Claude Code needs to create an isolated worktree. The hook is responsible for actually creating the worktree and reporting its path. When it fires: When worktree isolation is requested for a task. Input fields:
name
string
Suggested slug for the worktree directory.
Behavior: Write the absolute path of the created worktree directory to stdout and exit with code 0. Any other exit code signals a failure.

WorktreeRemove

Fires when Claude Code needs to remove a previously created worktree. When it fires: When a worktree task completes and the worktree should be cleaned up. Input fields:
worktree_path
string
Absolute path of the worktree to remove.
Behavior: Exit code 0 means success. Other exit codes show stderr to the user.

CwdChanged

Fires after the working directory changes. When it fires: When Claude changes directories during a session. Input fields:
old_cwd
string
Previous working directory.
new_cwd
string
New working directory.
hookSpecificOutput fields:
watchPaths
string[]
Absolute paths to add to the FileChanged watcher. Return this to start watching files in the new directory.
The CLAUDE_ENV_FILE environment variable is set — write export KEY=value lines to apply environment variables to subsequent Bash tool commands.

FileChanged

Fires when a watched file is modified, added, or removed. When it fires: When a file registered via SessionStart.watchPaths or CwdChanged.watchPaths changes on disk. Input fields:
file_path
string
Absolute path of the changed file.
event
'change' | 'add' | 'unlink'
The type of filesystem event.
hookSpecificOutput fields:
watchPaths
string[]
Update the watch list with these absolute paths.
The CLAUDE_ENV_FILE environment variable is set for this hook as well.

Async hooks

For hooks that need to run in the background without delaying Claude, output an async acknowledgment on stdout instead of the normal sync output:
{
  "async": true,
  "asyncTimeout": 30
}
async
literal: true
required
Signals that this hook is running asynchronously.
asyncTimeout
number
How long (in seconds) the async hook is allowed to run before it is cancelled.
Async hooks cannot block tool execution or inject context. Use them for side effects like notifications, logging, or metrics that must not slow down the agentic loop.