---
title: "Claude Code Hooks Events Reference: All 30 Events, Matchers & Schemas"
description: "Complete reference for all 30 Claude Code hook events: matcher fields, input schemas, decision control, and timeout defaults, with copy-paste JSON examples for each event."
tags: [reference, hooks]
---

# 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`.

See also: [Event-Driven Agent Automation](../workflows/event-driven-agents.md) for building hook-driven workflows, and [Security Hardening: Startup Hooks](../security/security-hardening.md#16-startup-hooks-code-execution-before-your-first-prompt) for the risks of executing code via hooks.

---

## Quick Reference

| Event | Fires when | Matcher field | Can block? | Default timeout |
|-------|-----------|---------------|------------|----------------|
| `SessionStart` | Session begins or resumes | `source` | No | 600s |
| `Setup` | `--init-only` or `-p --init/--maintenance` | `trigger` | No | 600s |
| `UserPromptSubmit` | User submits a prompt | none | Yes | **30s** |
| `UserPromptExpansion` | Slash command expands to prompt | `command_name` | Yes | 600s |
| `PreToolUse` | Before tool call executes | `tool_name` | Yes | 600s |
| `PermissionRequest` | Permission dialog is about to appear | `tool_name` | Yes (via JSON) | 600s |
| `PermissionDenied` | Auto-mode classifier denies a call | `tool_name` | No (retry only) | 600s |
| `PostToolUse` | After tool call succeeds | `tool_name` | No (stderr to Claude) | 600s |
| `PostToolUseFailure` | After tool call fails | `tool_name` | No | 600s |
| `PostToolBatch` | After full parallel batch resolves | none | Yes (stops loop) | 600s |
| `Notification` | Claude sends a notification | `notification_type` | No | 600s |
| `MessageDisplay` | Assistant message text streams | none | No | **10s** |
| `SubagentStart` | Subagent spawned via Agent tool | `agent_type` | No | 600s |
| `SubagentStop` | Subagent finishes | `agent_type` | Yes | 600s |
| `TaskCreated` | Task being created via TaskCreate | none | Yes | 600s |
| `TaskCompleted` | Task being marked as completed | none | Yes | 600s |
| `Stop` | Claude finishes responding | none | Yes (continues turn) | 600s |
| `StopFailure` | Turn ends due to API error | `error` | No (ignored) | 600s |
| `TeammateIdle` | Agent team teammate goes idle | none | Yes | 600s |
| `InstructionsLoaded` | CLAUDE.md or rules file loaded | `load_reason` | No | 600s |
| `ConfigChange` | Config file changes during session | `source` | Yes (not `policy_settings`) | 600s |
| `CwdChanged` | Working directory changes | none | No | 600s |
| `FileChanged` | Watched file changes on disk | filename (literal) | No | 600s |
| `WorktreeCreate` | Worktree being created | none | Yes (any non-zero) | 600s |
| `WorktreeRemove` | Worktree being removed | none | No | 600s |
| `PreCompact` | Before context compaction | `trigger` | Yes | 600s |
| `PostCompact` | After compaction completes | `trigger` | No | 600s |
| `Elicitation` | MCP server requests user input | `mcp_server_name` | Yes | 600s |
| `ElicitationResult` | User responds to MCP elicitation | `mcp_server_name` | Yes | 600s |
| `SessionEnd` | Session terminates | `reason` | No | **1.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.

---

## Matcher Values by Event

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

### Tool events: matcher filters on `tool_name`

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).

### SessionStart: matcher filters on `source`

| Value | When |
|-------|------|
| `startup` | New session |
| `resume` | `--resume`, `--continue`, or `/resume` |
| `clear` | `/clear` |
| `compact` | Auto or manual compaction |

### Setup: matcher filters on `trigger`

| Value | When |
|-------|------|
| `init` | `claude --init-only` or `claude -p --init` |
| `maintenance` | `claude -p --maintenance` |

### SessionEnd: matcher filters on `reason`

| Value | When |
|-------|------|
| `clear` | `/clear` command |
| `resume` | Interactive `/resume` switch |
| `logout` | User logged out |
| `prompt_input_exit` | Exited while prompt input was visible |
| `bypass_permissions_disabled` | Bypass mode disabled |
| `other` | Other exit reasons |

### 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`

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`

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

### InstructionsLoaded: matcher filters on `load_reason`

Values: `session_start`, `nested_traversal`, `path_glob_match`, `include`, `compact`

### ConfigChange: matcher filters on `source`

Values: `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills`

### StopFailure: matcher filters on `error`

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`

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

### Elicitation / ElicitationResult: matcher filters on `mcp_server_name`

Your configured MCP server name.

### FileChanged: matcher = literal filenames

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.

### Events with no matcher support

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

Adding a `matcher` field to these events is silently ignored.

---

## Exit Code 2 Behavior Per Event

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.

| Event | What happens on exit 2 |
|-------|------------------------|
| `PreToolUse` | Blocks the tool call; stderr fed to Claude |
| `PermissionRequest` | Denies the permission |
| `UserPromptSubmit` | Blocks prompt and erases it from context |
| `UserPromptExpansion` | Blocks the expansion |
| `Stop` | Prevents stopping; continues the turn |
| `SubagentStop` | Prevents subagent from stopping |
| `TeammateIdle` | Teammate continues working; stderr fed back |
| `TaskCreated` | Rolls back task creation; stderr fed back |
| `TaskCompleted` | Prevents completion; stderr fed back |
| `ConfigChange` | Blocks config change (not `policy_settings`) |
| `PostToolBatch` | Stops agentic loop before next model call |
| `PreCompact` | Blocks compaction |
| `Elicitation` | Denies the elicitation |
| `ElicitationResult` | Blocks response (effective action becomes decline) |
| `WorktreeCreate` | **Any** non-zero exit code fails creation |
| `PostToolUse` | Shows stderr to Claude (tool already ran) |
| `PostToolUseFailure` | Shows stderr to Claude |
| `StopFailure` | Ignored entirely (output and exit code ignored) |
| `SessionEnd` | Shows stderr to user only |
| `SessionStart`, `Setup`, `SubagentStart` | Shows stderr to user only |
| `Notification` | Shows stderr to user only |
| `InstructionsLoaded` | Exit code ignored |
| `PermissionDenied` | Exit code and stderr ignored |
| `CwdChanged`, `FileChanged`, `WorktreeRemove` | Logged in debug mode only |
| `PostCompact` | Shows stderr to user only |
| `MessageDisplay` | Original text displayed unchanged |

---

## Decision Control Format Per Event

### Top-level `decision` field

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

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

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

### PreToolUse

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

```json
{
  "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.

### PermissionRequest

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

### PermissionDenied

Exit code and stderr are ignored. To signal retry:

```json
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionDenied",
    "retry": true
  }
}
```

### PostToolUse

```json
{
  "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.

### WorktreeCreate

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

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

Any failure or missing path fails worktree creation.

### MessageDisplay

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

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

### SessionStart (additional fields)

```json
{
  "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.

### Elicitation

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

### ElicitationResult

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

### Universal fields (all events)

```json
{
  "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.

---

## Key Input Fields Per Event

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

| Event | Event-specific extra input fields |
|-------|----------------------------------|
| `SessionStart` | `source`, `model`, optionally `agent_type`, `session_title` |
| `Setup` | `trigger` (`"init"` or `"maintenance"`) |
| `UserPromptSubmit` | `prompt` |
| `UserPromptExpansion` | `expansion_type`, `command_name`, `command_args`, `command_source`, `prompt` |
| `PreToolUse` | `tool_name`, `tool_input`, `tool_use_id` |
| `PermissionRequest` | `tool_name`, `tool_input`, `permission_suggestions` |
| `PermissionDenied` | `tool_name`, `tool_input`, `tool_use_id`, `reason` |
| `PostToolUse` | `tool_name`, `tool_input`, `tool_response`, `tool_use_id`, `duration_ms` |
| `PostToolUseFailure` | `tool_name`, `tool_input`, `tool_use_id`, `error`, `is_interrupt`, `duration_ms` |
| `PostToolBatch` | `tool_calls` (array with `tool_name`, `tool_input`, `tool_use_id`, `tool_response`) |
| `Notification` | `message`, `title`, `notification_type` |
| `MessageDisplay` | `turn_id`, `message_id`, `index`, `final`, `delta` |
| `SubagentStart` | `agent_id`, `agent_type` |
| `SubagentStop` | `stop_hook_active`, `agent_id`, `agent_type`, `agent_transcript_path`, `last_assistant_message` |
| `TaskCreated` | `task_id`, `task_subject`, `task_description`, `teammate_name`, `team_name` |
| `TaskCompleted` | `task_id`, `task_subject`, `task_description`, `teammate_name`, `team_name` |
| `Stop` | `stop_hook_active`, `last_assistant_message`, `background_tasks`, `session_crons` |
| `StopFailure` | `error`, `error_details`, `last_assistant_message` |
| `TeammateIdle` | `teammate_name`, `team_name` |
| `InstructionsLoaded` | `file_path`, `memory_type`, `load_reason`, `globs`, `trigger_file_path`, `parent_file_path` |
| `ConfigChange` | `source`, `file_path` |
| `CwdChanged` | `old_cwd`, `new_cwd` |
| `FileChanged` | `file_path`, `event` (`"change"`, `"add"`, `"unlink"`) |
| `WorktreeCreate` | `name` (slug for the new worktree) |
| `WorktreeRemove` | `worktree_path` |
| `PreCompact` | `trigger`, `custom_instructions` |
| `PostCompact` | `trigger`, `compact_summary` |
| `Elicitation` | `mcp_server_name`, `message`, `mode`, `url`, `elicitation_id`, `requested_schema` |
| `ElicitationResult` | `mcp_server_name`, `action`, `mode`, `elicitation_id`, `content` |
| `SessionEnd` | `reason` |

---

## Hook Handler Fields

### Common fields (all types)

| Field | Description |
|-------|-------------|
| `type` | `command`, `http`, `mcp_tool`, `prompt`, or `agent` |
| `if` | Permission 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 |
| `timeout` | Seconds before canceling |
| `statusMessage` | Custom spinner message while hook runs |
| `once` | Run once per session then remove. Honored only in skill frontmatter, ignored in settings files |

### Command hook fields

| Field | Description |
|-------|-------------|
| `command` | Shell command or executable path |
| `args` | Argument vector: triggers exec form (no shell involved) |
| `async` | `true` to run in background without blocking |
| `asyncRewake` | Like `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}`.

### HTTP hook fields

| Field | Description |
|-------|-------------|
| `url` | POST endpoint URL |
| `headers` | Additional headers (supports `$VAR` interpolation) |
| `allowedEnvVars` | Env 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.

### MCP tool hook fields

| Field | Description |
|-------|-------------|
| `server` | Connected MCP server name |
| `tool` | Tool name on that server |
| `input` | Tool arguments. String values support `${path}` substitution from hook input |

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

### Prompt hook fields

| Field | Description |
|-------|-------------|
| `prompt` | Prompt text. Use `$ARGUMENTS` for the hook's JSON input |
| `model` | Model override (default: fast model, typically Haiku) |
| `continueOnBlock` | When `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`.

### Agent hook fields

| Field | Description |
|-------|-------------|
| `prompt` | Task description. Use `$ARGUMENTS` for the hook's JSON input |
| `model` | Model override |

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

---

## Path Placeholders

| Placeholder | Resolves 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.

---

## CLAUDE_ENV_FILE

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.

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

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

---

## Common Gotchas

**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.
