Hooks are lifecycle callbacks that fire at specific points during Claude’s execution — before a tool runs, after a file is edited, when a session starts, and 19 other moments. With 22 event types and 4 handler types (command, HTTP, prompt, agent), hooks are the deepest extensibility point in the CLI. This chapter is the full configuration reference; for security-focused patterns like blocking destructive commands, see the Hooks as Guardrails chapter.
Event Types
Every hook targets one of 22 events. Each event fires at a specific moment in Claude’s lifecycle and optionally supports a matcher to narrow scope.
Session & Configuration Events
| Event | When It Fires | Matcher |
|---|---|---|
SessionStart | Session begins or resumes | Session source: startup, resume, clear, compact |
SessionEnd | Session terminates | Exit reason: clear, logout, prompt_input_exit |
ConfigChange | A config file changes | Config source: user_settings, project_settings, etc. |
InstructionsLoaded | CLAUDE.md or rules loaded | No matcher support |
User Interaction Events
| Event | When It Fires | Matcher |
|---|---|---|
UserPromptSubmit | User submits a prompt | No matcher support |
PermissionRequest | Permission dialog appears | Tool name |
Notification | Claude sends a notification | Notification type: permission_prompt, idle_prompt |
Tool Execution Events
| Event | When It Fires | Matcher |
|---|---|---|
PreToolUse | Before a tool executes | Tool name: Bash, Edit|Write, mcp__.* |
PostToolUse | After a tool succeeds | Tool name |
PostToolUseFailure | After a tool fails | Tool name |
Agent Management Events
| Event | When It Fires | Matcher |
|---|---|---|
SubagentStart | Subagent spawned | Agent type name |
SubagentStop | Subagent finishes | Agent type name |
Stop | Main Claude finishes responding | No matcher support |
TeammateIdle | Team agent going idle | None |
TaskCompleted | Task marked complete | None |
Context & Isolation Events
| Event | When It Fires | Matcher |
|---|---|---|
PreCompact | Before context compaction | Trigger: manual, auto |
PostCompact | After context compaction | Trigger: manual, auto |
WorktreeCreate | Worktree being created | No matcher support |
WorktreeRemove | Worktree being removed | No matcher support |
MCP Integration Events
| Event | When It Fires | Matcher |
|---|---|---|
Elicitation | MCP server requests user input | None |
ElicitationResult | User responds to MCP elicitation | None |
Handler Types
Each hook entry specifies one of four handler types that determine how the hook runs.
Handler Types
| Type | Behavior | Use Case |
|---|---|---|
command | Executes a shell command. Hook input arrives on stdin as JSON. Supports timeout, async, and statusMessage options. | Bash validators, linters, log scripts, any local automation |
http | Sends a POST request with JSON body to a URL. Supports headers with env var interpolation and allowedEnvVars whitelisting. | Audit logging to external services, webhook-based policy enforcement |
prompt | Sends hook input to a Claude model for evaluation. Uses $ARGUMENTS placeholder in the prompt text, replaced with the JSON input. | AI-powered command validation, semantic policy checks |
agent | Spawns a subagent with tool access to evaluate the hook input. Most powerful but slowest handler type. | Complex verification tasks that require reading files or running commands |
Command Handler
The most common handler type. The shell command receives hook context on stdin as JSON and communicates its decision via exit codes.
{ "type": "command", "command": "./.claude/hooks/validator.sh", "timeout": 600, "async": false, "statusMessage": "Validating..."}command(required): shell command to runtimeout(optional): seconds before cancel, default 600async(optional): run in background without blocking ClaudestatusMessage(optional): spinner text shown during execution
HTTP Handler
Posts the JSON input to an external endpoint. Headers support environment variable interpolation with $VAR_NAME syntax, but only for variables listed in allowedEnvVars.
{ "type": "http", "url": "http://localhost:8080/hooks/validate", "headers": {"Authorization": "Bearer $MY_TOKEN"}, "allowedEnvVars": ["MY_TOKEN"], "timeout": 30}Prompt Handler
Delegates the decision to a Claude model. The $ARGUMENTS placeholder in your prompt text is replaced with the full JSON input from the hook event.
{ "type": "prompt", "prompt": "Should this bash command run? $ARGUMENTS\n\nDeny commands with 'rm -rf'.", "model": "claude-3-5-haiku-20241022", "timeout": 30}Agent Handler
Spawns a full subagent with tool access. Use this when evaluation requires reading files, running commands, or multi-step reasoning.
{ "type": "agent", "prompt": "Verify this operation is safe: $ARGUMENTS", "timeout": 60}Configuration
Hooks are defined in settings.json under the hooks key. The structure nests event name, then an array of matcher+hooks pairs, then an array of handler objects within each pair.
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "./.claude/hooks/block-rm.sh" } ] }, { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "./scripts/validate-edit.sh", "timeout": 10, "statusMessage": "Validating edit..." } ] } ], "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "./scripts/run-linter.sh", "timeout": 30, "statusMessage": "Running linter..." } ] } ] }}Configuration Scopes
Hooks can be defined at multiple levels, from personal defaults to project-specific rules.
Where to Put Hooks
| Location | Scope | Use Case |
|---|---|---|
~/.claude/settings.json | All projects | Personal safety rules that follow you everywhere |
.claude/settings.json | This project | Team-shared hooks, committed to git |
.claude/settings.local.json | This project, local only | Local-only hooks, gitignored |
Plugin hooks/hooks.json | Where plugin is enabled | Distributed hooks (only http and prompt types allowed) |
| Agent/Skill frontmatter | While component is active | Hooks scoped to a specific agent or skill lifecycle |
Hooks do not walk up the directory tree. A parent directory’s .claude/settings.json hooks do not fire when running from a subfolder — even if the subfolder has no settings.json of its own. This is different from skills and MCP configs (which do inherit upward). Each subfolder that needs hook enforcement must define its own .claude/settings.json. For org-wide enforcement, use managed settings.
The /hooks Browser
Inside an interactive Claude session, type /hooks to open the built-in hook browser. It lists all registered hooks across every scope, shows which events they target, and lets you verify your configuration is loaded correctly. Pair it with --debug "hooks" when troubleshooting execution order or matcher issues.
Event Matchers
Matchers are regex patterns that filter which specific instances of an event trigger your hook. Without a matcher, the hook fires for every occurrence of that event type.
{"matcher": "Bash"} // Exact tool name{"matcher": "Edit|Write"} // Multiple tools (regex OR){"matcher": "mcp__memory__.*"} // All MCP memory server tools{"matcher": "mcp__.*__write.*"} // Any MCP write operationMatchers are case-sensitive and use full regex syntax. This means Bash* does not mean “anything starting with Bash” — it means “Bas” followed by zero or more “h” characters. For exact tool matching, just use the tool name directly: Bash.
For Notification events, the matcher targets the notification type (permission_prompt, idle_prompt) rather than a tool name. For SessionStart, it matches the session source (startup, resume, clear, compact).
Omitting the matcher field means the hook runs on every event of that type. For PreToolUse, that means every single tool call — Bash, Edit, Write, Read, Glob, Grep, and all MCP tools. Be intentional about whether you want broad or narrow scope.
Exit Code Behavior
Command handlers communicate their decision through exit codes. The convention is specific to the hooks system and differs from typical Unix expectations.
Exit Codes
| Exit Code | Meaning | Behavior |
|---|---|---|
0 | Success / Allow | Action proceeds. If the hook printed JSON to stdout, it is processed for advanced control. |
2 | Block | Action is prevented. Stderr output is fed back to Claude as the reason. |
| Other | Non-blocking error | Hook failure is logged. Stderr shown in verbose mode. Action still proceeds. |
What exit code 2 blocks depends on the event:
Exit Code 2 Blocking by Event
| Event | What Gets Blocked |
|---|---|
PreToolUse | Tool call is prevented entirely |
PermissionRequest | Permission is denied |
UserPromptSubmit | Prompt is not processed |
Stop, SubagentStop | Agent is prevented from stopping |
ConfigChange | Config change is blocked |
PostToolUse | Nothing — the tool already ran. Stderr is shown but the action cannot be undone. |
Hooks in the same event array run sequentially in array order. The first hook to exit with code 2 wins — subsequent hooks in the array are not executed.
In standard Unix, exit code 0 means success and non-zero means failure. The hooks system reuses 0 for “allow” but specifically reserves exit code 2 for “block.” Exit code 1 (the typical Unix error) is treated as a non-blocking hook failure, not a block. If your script accidentally exits with 1, the action will still proceed — only exit code 2 actually prevents anything.
Command handlers do not receive arguments on the command line. Instead, the full hook context — including tool_input, tool name, and event metadata — arrives as a JSON object on stdin. Your script must read from stdin (e.g., INPUT=$(cat)) and parse it with a tool like jq. Forgetting to read stdin is the most common cause of hooks that silently do nothing.
Environment Variables
Command hooks automatically receive several environment variables:
CLAUDE_PROJECT_DIR— project root directoryCLAUDE_PLUGIN_ROOT— plugin installation directoryCLAUDE_PLUGIN_DATA— plugin persistent data directoryCLAUDE_ENV_FILE— (SessionStart only) file path for persisting environment variables across the session
The CLAUDE_ENV_FILE variable deserves special attention. In a SessionStart hook, you can write export statements to this file to set environment variables that persist for all subsequent Bash commands in the session:
#!/bin/bashif [ -n "$CLAUDE_ENV_FILE" ]; then echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE" echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"fiexit 0Use —debug “hooks” to see exactly which hooks fire, in what order, and what input they receive. This is the fastest way to diagnose hooks that are not matching or are receiving unexpected data.