4 4
5# Hooks reference5# Hooks reference
6 6
7> Reference for Claude Code hook events, configuration schema, JSON input/output formats, exit codes, async hooks, prompt hooks, and MCP tool hooks.7> Reference for Claude Code hook events, configuration schema, JSON input/output formats, exit codes, async hooks, HTTP hooks, prompt hooks, and MCP tool hooks.
8 8
9<Tip>9<Tip>
10 For a quickstart guide with examples, see [Automate workflows with hooks](/en/hooks-guide).10 For a quickstart guide with examples, see [Automate workflows with hooks](/en/hooks-guide).
11</Tip>11</Tip>
12 12
13Hooks are user-defined shell commands or LLM prompts that execute automatically at specific points in Claude Code's lifecycle. Use this reference to look up event schemas, configuration options, JSON input/output formats, and advanced features like async hooks and MCP tool hooks. If you're setting up hooks for the first time, start with the [guide](/en/hooks-guide) instead.13Hooks are user-defined shell commands, HTTP endpoints, or LLM prompts that execute automatically at specific points in Claude Code's lifecycle. Use this reference to look up event schemas, configuration options, JSON input/output formats, and advanced features like async hooks, HTTP hooks, and MCP tool hooks. If you're setting up hooks for the first time, start with the [guide](/en/hooks-guide) instead.
14 14
15## Hook lifecycle15## Hook lifecycle
16 16
17Hooks fire at specific points during a Claude Code session. When an event fires and a matcher matches, Claude Code passes JSON context about the event to your hook handler. For command hooks, this arrives on stdin. Your handler can then inspect the input, take action, and optionally return a decision. Some events fire once per session, while others fire repeatedly inside the agentic loop:17Hooks fire at specific points during a Claude Code session. When an event fires and a matcher matches, Claude Code passes JSON context about the event to your hook handler. For command hooks, input arrives on stdin. For HTTP hooks, it arrives as the POST request body. Your handler can then inspect the input, take action, and optionally return a decision. Some events fire once per session, while others fire repeatedly inside the agentic loop:
18 18
19<div style={{maxWidth: "500px", margin: "0 auto"}}>19<div style={{maxWidth: "500px", margin: "0 auto"}}>
20 <Frame>20 <Frame>
21 <img src="https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=5c25fedbc3db6f8882af50c3cc478c32" alt="Hook lifecycle diagram showing the sequence of hooks from SessionStart through the agentic loop to SessionEnd" data-og-width="8876" width="8876" data-og-height="12492" height="12492" data-path="images/hooks-lifecycle.png" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=280&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=62406fcd5d4a189cc8842ee1bd946b84 280w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=560&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=fa3049022a6973c5f974e0f95b28169d 560w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=840&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=bd2890897db61a03160b93d4f972ff8e 840w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=1100&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=7ae8e098340479347135e39df4a13454 1100w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=1650&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=848a8606aab22c2ccaa16b6a18431e32 1650w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=2500&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=f3a9ef7feb61fa8fe362005aa185efbc 2500w" />21 <img src="https://mintcdn.com/claude-code/2YzYcIR7V1VggfgF/images/hooks-lifecycle.svg?fit=max&auto=format&n=2YzYcIR7V1VggfgF&q=85&s=3004e6c5dc95c4fe7fa3eb40fdc4176c" alt="Hook lifecycle diagram showing the sequence of hooks from SessionStart through the agentic loop (PreToolUse, PermissionRequest, PostToolUse, SubagentStart/Stop, TaskCompleted) to Stop or StopFailure, TeammateIdle, PreCompact, PostCompact, and SessionEnd, with Elicitation and ElicitationResult nested inside MCP tool execution and WorktreeCreate, WorktreeRemove, Notification, ConfigChange, and InstructionsLoaded as standalone async events" width="520" height="1100" data-path="images/hooks-lifecycle.svg" />
22 </Frame>22 </Frame>
23</div>23</div>
24 24
25The table below summarizes when each event fires. The [Hook events](#hook-events) section documents the full input schema and decision control options for each one.25The table below summarizes when each event fires. The [Hook events](#hook-events) section documents the full input schema and decision control options for each one.
26 26
27| Event | When it fires |27| Event | When it fires |
28| :------------------- | :--------------------------------------------------- |28| :------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- |
29| `SessionStart` | When a session begins or resumes |29| `SessionStart` | When a session begins or resumes |
30| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |30| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |
31| `PreToolUse` | Before a tool call executes. Can block it |31| `PreToolUse` | Before a tool call executes. Can block it |
36| `SubagentStart` | When a subagent is spawned |36| `SubagentStart` | When a subagent is spawned |
37| `SubagentStop` | When a subagent finishes |37| `SubagentStop` | When a subagent finishes |
38| `Stop` | When Claude finishes responding |38| `Stop` | When Claude finishes responding |
39| `StopFailure` | When the turn ends due to an API error. Output and exit code are ignored |
40| `TeammateIdle` | When an [agent team](/en/agent-teams) teammate is about to go idle |
41| `TaskCompleted` | When a task is being marked as completed |
42| `InstructionsLoaded` | When a CLAUDE.md or `.claude/rules/*.md` file is loaded into context. Fires at session start and when files are lazily loaded during a session |
43| `ConfigChange` | When a configuration file changes during a session |
44| `WorktreeCreate` | When a worktree is being created via `--worktree` or `isolation: "worktree"`. Replaces default git behavior |
45| `WorktreeRemove` | When a worktree is being removed, either at session exit or when a subagent finishes |
39| `PreCompact` | Before context compaction |46| `PreCompact` | Before context compaction |
47| `PostCompact` | After context compaction completes |
48| `Elicitation` | When an MCP server requests user input during a tool call |
49| `ElicitationResult` | After a user responds to an MCP elicitation, before the response is sent back to the server |
40| `SessionEnd` | When a session terminates |50| `SessionEnd` | When a session terminates |
41 51
42### How a hook resolves52### How a hook resolves
61}71}
62```72```
63 73
64The script reads the JSON input from stdin, extracts the command, and blocks it if it contains `rm -rf`:74The script reads the JSON input from stdin, extracts the command, and returns a `permissionDecision` of `"deny"` if it contains `rm -rf`:
65 75
66```bash theme={null}76```bash theme={null}
67#!/bin/bash77#!/bin/bash
69COMMAND=$(jq -r '.tool_input.command')79COMMAND=$(jq -r '.tool_input.command')
70 80
71if echo "$COMMAND" | grep -q 'rm -rf'; then81if echo "$COMMAND" | grep -q 'rm -rf'; then
72 echo '{"decision":"block","reason":"Destructive command blocked by hook"}'82 jq -n '{
83 hookSpecificOutput: {
84 hookEventName: "PreToolUse",
85 permissionDecision: "deny",
86 permissionDecisionReason: "Destructive command blocked by hook"
87 }
88 }'
73else89else
74 exit 0 # allow the command90 exit 0 # allow the command
75fi91fi
78Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:94Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:
79 95
80<Frame>96<Frame>
81 <img src="https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=7c13f51ffcbc37d22a593b27e2f2de72" alt="Hook resolution flow: PreToolUse event fires, matcher checks for Bash match, hook handler runs, result returns to Claude Code" data-og-width="780" width="780" data-og-height="290" height="290" data-path="images/hook-resolution.svg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=280&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=36a39a07e8bc1995dcb4639e09846905 280w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=560&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=6568d90c596c7605bbac2c325b0a0c86 560w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=840&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=255a6f68b9475a0e41dbde7b88002dad 840w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=1100&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=dcecf8d5edc88cd2bc49deb006d5760d 1100w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=1650&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=04fe51bf69ae375e9fd517f18674e35f 1650w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=2500&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=b1b76e0b77fddb5c7fa7bf302dacd80b 2500w" />97 <img src="https://mintcdn.com/claude-code/c5r9_6tjPMzFdDDT/images/hook-resolution.svg?fit=max&auto=format&n=c5r9_6tjPMzFdDDT&q=85&s=ad667ee6d86ab2276aa48a4e73e220df" alt="Hook resolution flow: PreToolUse event fires, matcher checks for Bash match, hook handler runs, result returns to Claude Code" width="780" height="290" data-path="images/hook-resolution.svg" />
82</Frame>98</Frame>
83 99
84<Steps>100<Steps>
98 The script extracts `"rm -rf /tmp/build"` from the input and finds `rm -rf`, so it prints a decision to stdout:114 The script extracts `"rm -rf /tmp/build"` from the input and finds `rm -rf`, so it prints a decision to stdout:
99 115
100 ```json theme={null}116 ```json theme={null}
101 { "decision": "block", "reason": "Destructive command blocked by hook" }117 {
118 "hookSpecificOutput": {
119 "hookEventName": "PreToolUse",
120 "permissionDecision": "deny",
121 "permissionDecisionReason": "Destructive command blocked by hook"
122 }
123 }
102 ```124 ```
103 125
104 If the command had been safe (like `npm test`), the script would hit `exit 0` instead, which tells Claude Code to allow the tool call with no further action.126 If the command had been safe (like `npm test`), the script would hit `exit 0` instead, which tells Claude Code to allow the tool call with no further action.
122See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.144See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.
123 145
124<Note>146<Note>
125 This page uses specific terms for each level: **hook event** for the lifecycle point, **matcher group** for the filter, and **hook handler** for the shell command, prompt, or agent that runs. "Hook" on its own refers to the general feature.147 This page uses specific terms for each level: **hook event** for the lifecycle point, **matcher group** for the filter, and **hook handler** for the shell command, HTTP endpoint, prompt, or agent that runs. "Hook" on its own refers to the general feature.
126</Note>148</Note>
127 149
128### Hook locations150### Hook locations
145The `matcher` field is a regex string that filters when hooks fire. Use `"*"`, `""`, or omit `matcher` entirely to match all occurrences. Each event type matches on a different field:167The `matcher` field is a regex string that filters when hooks fire. Use `"*"`, `""`, or omit `matcher` entirely to match all occurrences. Each event type matches on a different field:
146 168
147| Event | What the matcher filters | Example matcher values |169| Event | What the matcher filters | Example matcher values |
148| :--------------------------------------------------------------------- | :------------------------ | :----------------------------------------------------------------------------- |170| :---------------------------------------------------------------------------------------------- | :------------------------ | :------------------------------------------------------------------------------------------------------------------------ |
149| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |171| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |
150| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |172| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |
151| `SessionEnd` | why the session ended | `clear`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |173| `SessionEnd` | why the session ended | `clear`, `resume`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |
152| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |174| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |
153| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |175| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |
154| `PreCompact` | what triggered compaction | `manual`, `auto` |176| `PreCompact`, `PostCompact` | what triggered compaction | `manual`, `auto` |
155| `SubagentStop` | agent type | same values as `SubagentStart` |177| `SubagentStop` | agent type | same values as `SubagentStart` |
156| `UserPromptSubmit`, `Stop` | no matcher support | always fires on every occurrence |178| `ConfigChange` | configuration source | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` |
179| `StopFailure` | error type | `rate_limit`, `authentication_failed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, `unknown` |
180| `InstructionsLoaded` | load reason | `session_start`, `nested_traversal`, `path_glob_match`, `include`, `compact` |
181| `Elicitation` | MCP server name | your configured MCP server names |
182| `ElicitationResult` | MCP server name | same values as `Elicitation` |
183| `UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove` | no matcher support | always fires on every occurrence |
157 184
158The matcher is a regex, so `Edit|Write` matches either tool and `Notebook.*` matches any tool starting with Notebook. The matcher runs against a field from the [JSON input](#hook-input-and-output) that Claude Code sends to your hook on stdin. For tool events, that field is `tool_name`. Each [hook event](#hook-events) section lists the full set of matcher values and the input schema for that event.185The matcher is a regex, so `Edit|Write` matches either tool and `Notebook.*` matches any tool starting with Notebook. The matcher runs against a field from the [JSON input](#hook-input-and-output) that Claude Code sends to your hook on stdin. For tool events, that field is `tool_name`. Each [hook event](#hook-events) section lists the full set of matcher values and the input schema for that event.
159 186
177}204}
178```205```
179 206
180`UserPromptSubmit` and `Stop` don't support matchers and always fire on every occurrence. If you add a `matcher` field to these events, it is silently ignored.207`UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCompleted`, `WorktreeCreate`, and `WorktreeRemove` don't support matchers and always fire on every occurrence. If you add a `matcher` field to these events, it is silently ignored.
181 208
182#### Match MCP tools209#### Match MCP tools
183 210
225 252
226### Hook handler fields253### Hook handler fields
227 254
228Each object in the inner `hooks` array is a hook handler: the shell command, LLM prompt, or agent that runs when the matcher matches. There are three types:255Each object in the inner `hooks` array is a hook handler: the shell command, HTTP endpoint, LLM prompt, or agent that runs when the matcher matches. There are four types:
229 256
230* **[Command hooks](#command-hook-fields)** (`type: "command"`): run a shell command. Your script receives the event's [JSON input](#hook-input-and-output) on stdin and communicates results back through exit codes and stdout.257* **[Command hooks](#command-hook-fields)** (`type: "command"`): run a shell command. Your script receives the event's [JSON input](#hook-input-and-output) on stdin and communicates results back through exit codes and stdout.
258* **[HTTP hooks](#http-hook-fields)** (`type: "http"`): send the event's JSON input as an HTTP POST request to a URL. The endpoint communicates results back through the response body using the same [JSON output format](#json-output) as command hooks.
231* **[Prompt hooks](#prompt-and-agent-hook-fields)** (`type: "prompt"`): send a prompt to a Claude model for single-turn evaluation. The model returns a yes/no decision as JSON. See [Prompt-based hooks](#prompt-based-hooks).259* **[Prompt hooks](#prompt-and-agent-hook-fields)** (`type: "prompt"`): send a prompt to a Claude model for single-turn evaluation. The model returns a yes/no decision as JSON. See [Prompt-based hooks](#prompt-based-hooks).
232* **[Agent hooks](#prompt-and-agent-hook-fields)** (`type: "agent"`): spawn a subagent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. See [Agent-based hooks](#agent-based-hooks).260* **[Agent hooks](#prompt-and-agent-hook-fields)** (`type: "agent"`): spawn a subagent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. See [Agent-based hooks](#agent-based-hooks).
233 261
237 265
238| Field | Required | Description |266| Field | Required | Description |
239| :-------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |267| :-------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |
240| `type` | yes | `"command"`, `"prompt"`, or `"agent"` |268| `type` | yes | `"command"`, `"http"`, `"prompt"`, or `"agent"` |
241| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |269| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |
242| `statusMessage` | no | Custom spinner message displayed while the hook runs |270| `statusMessage` | no | Custom spinner message displayed while the hook runs |
243| `once` | no | If `true`, runs only once per session then is removed. Skills only, not agents. See [Hooks in skills and agents](#hooks-in-skills-and-agents) |271| `once` | no | If `true`, runs only once per session then is removed. Skills only, not agents. See [Hooks in skills and agents](#hooks-in-skills-and-agents) |
251| `command` | yes | Shell command to execute |279| `command` | yes | Shell command to execute |
252| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |280| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |
253 281
282#### HTTP hook fields
283
284In addition to the [common fields](#common-fields), HTTP hooks accept these fields:
285
286| Field | Required | Description |
287| :--------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
288| `url` | yes | URL to send the POST request to |
289| `headers` | no | Additional HTTP headers as key-value pairs. Values support environment variable interpolation using `$VAR_NAME` or `${VAR_NAME}` syntax. Only variables listed in `allowedEnvVars` are resolved |
290| `allowedEnvVars` | no | List of environment variable names that may be interpolated into header values. References to unlisted variables are replaced with empty strings. Required for any env var interpolation to work |
291
292Claude Code sends the hook's [JSON input](#hook-input-and-output) as the POST request body with `Content-Type: application/json`. The response body uses the same [JSON output format](#json-output) as command hooks.
293
294Error handling differs from command hooks: non-2xx responses, connection failures, and timeouts all produce non-blocking errors that allow execution to continue. To block a tool call or deny a permission, return a 2xx response with a JSON body containing `decision: "block"` or a `hookSpecificOutput` with `permissionDecision: "deny"`.
295
296This example sends `PreToolUse` events to a local validation service, authenticating with a token from the `MY_TOKEN` environment variable:
297
298```json theme={null}
299{
300 "hooks": {
301 "PreToolUse": [
302 {
303 "matcher": "Bash",
304 "hooks": [
305 {
306 "type": "http",
307 "url": "http://localhost:8080/hooks/pre-tool-use",
308 "timeout": 30,
309 "headers": {
310 "Authorization": "Bearer $MY_TOKEN"
311 },
312 "allowedEnvVars": ["MY_TOKEN"]
313 }
314 ]
315 }
316 ]
317 }
318}
319```
320
254#### Prompt and agent hook fields321#### Prompt and agent hook fields
255 322
256In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:323In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:
260| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |327| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
261| `model` | no | Model to use for evaluation. Defaults to a fast model |328| `model` | no | Model to use for evaluation. Defaults to a fast model |
262 329
263All matching hooks run in parallel, and identical handlers are deduplicated automatically. Handlers run in the current directory with Claude Code's environment. The `$CLAUDE_CODE_REMOTE` environment variable is set to `"true"` in remote web environments and not set in the local CLI.330All matching hooks run in parallel, and identical handlers are deduplicated automatically. Command hooks are deduplicated by command string, and HTTP hooks are deduplicated by URL. Handlers run in the current directory with Claude Code's environment. The `$CLAUDE_CODE_REMOTE` environment variable is set to `"true"` in remote web environments and not set in the local CLI.
264 331
265### Reference scripts by path332### Reference scripts by path
266 333
267Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:334Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:
268 335
269* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.336* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.
270* `${CLAUDE_PLUGIN_ROOT}`: the plugin's root directory, for scripts bundled with a [plugin](/en/plugins).337* `${CLAUDE_PLUGIN_ROOT}`: the plugin's installation directory, for scripts bundled with a [plugin](/en/plugins). Changes on each plugin update.
338* `${CLAUDE_PLUGIN_DATA}`: the plugin's [persistent data directory](/en/plugins-reference#persistent-data-directory), for dependencies and state that should survive plugin updates.
271 339
272<Tabs>340<Tabs>
273 <Tab title="Project scripts">341 <Tab title="Project scripts">
348 416
349### The `/hooks` menu417### The `/hooks` menu
350 418
351Type `/hooks` in Claude Code to open the interactive hooks manager, where you can view, add, and delete hooks without editing settings files directly. For a step-by-step walkthrough, see [Set up your first hook](/en/hooks-guide#set-up-your-first-hook) in the guide.419Type `/hooks` in Claude Code to open a read-only browser for your configured hooks. The menu shows every hook event with a count of configured hooks, lets you drill into matchers, and shows the full details of each hook handler. Use it to verify configuration, check which settings file a hook came from, or inspect a hook's command, prompt, or URL.
420
421The menu displays all four hook types: `command`, `prompt`, `agent`, and `http`. Each hook is labeled with a `[type]` prefix and a source indicating where it was defined:
352 422
353Each hook in the menu is labeled with a bracket prefix indicating its source:423* `User`: from `~/.claude/settings.json`
424* `Project`: from `.claude/settings.json`
425* `Local`: from `.claude/settings.local.json`
426* `Plugin`: from a plugin's `hooks/hooks.json`
427* `Session`: registered in memory for the current session
428* `Built-in`: registered internally by Claude Code
354 429
355* `[User]`: from `~/.claude/settings.json`430Selecting a hook opens a detail view showing its event, matcher, type, source file, and the full command, prompt, or URL. The menu is read-only: to add, modify, or remove hooks, edit the settings JSON directly or ask Claude to make the change.
356* `[Project]`: from `.claude/settings.json`
357* `[Local]`: from `.claude/settings.local.json`
358* `[Plugin]`: from a plugin's `hooks/hooks.json`, read-only
359 431
360### Disable or remove hooks432### Disable or remove hooks
361 433
362To remove a hook, delete its entry from the settings JSON file, or use the `/hooks` menu and select the hook to delete it.434To remove a hook, delete its entry from the settings JSON file.
435
436To temporarily disable all hooks without removing them, set `"disableAllHooks": true` in your settings file. There is no way to disable an individual hook while keeping it in the configuration.
363 437
364To temporarily disable all hooks without removing them, set `"disableAllHooks": true` in your settings file or use the toggle in the `/hooks` menu. There is no way to disable an individual hook while keeping it in the configuration.438The `disableAllHooks` setting respects the managed settings hierarchy. If an administrator has configured hooks through managed policy settings, `disableAllHooks` set in user, project, or local settings cannot disable those managed hooks. Only `disableAllHooks` set at the managed settings level can disable managed hooks.
365 439
366Direct edits to hooks in settings files don't take effect immediately. Claude Code captures a snapshot of hooks at startup and uses it throughout the session. This prevents malicious or accidental hook modifications from taking effect mid-session without your review. If hooks are modified externally, Claude Code warns you and requires review in the `/hooks` menu before changes apply.440Direct edits to hooks in settings files are normally picked up automatically by the file watcher.
367 441
368## Hook input and output442## Hook input and output
369 443
370Hooks receive JSON data via stdin and communicate results through exit codes, stdout, and stderr. This section covers fields and behavior common to all events. Each event's section under [Hook events](#hook-events) includes its specific input schema and decision control options.444Command hooks receive JSON data via stdin and communicate results through exit codes, stdout, and stderr. HTTP hooks receive the same JSON as the POST request body and communicate results through the HTTP response body. This section covers fields and behavior common to all events. Each event's section under [Hook events](#hook-events) includes its specific input schema and decision control options.
371 445
372### Common input fields446### Common input fields
373 447
374All hook events receive these fields via stdin as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section:448Hook events receive these fields as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section. For command hooks, this JSON arrives via stdin. For HTTP hooks, it arrives as the POST request body.
375 449
376| Field | Description |450| Field | Description |
377| :---------------- | :--------------------------------------------------------------------------------------------------------------------------------- |451| :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
378| `session_id` | Current session identifier |452| `session_id` | Current session identifier |
379| `transcript_path` | Path to conversation JSON |453| `transcript_path` | Path to conversation JSON |
380| `cwd` | Current working directory when the hook is invoked |454| `cwd` | Current working directory when the hook is invoked |
381| `permission_mode` | Current [permission mode](/en/iam#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"dontAsk"`, or `"bypassPermissions"` |455| `permission_mode` | Current [permission mode](/en/permissions#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"auto"`, `"dontAsk"`, or `"bypassPermissions"`. Not all events receive this field: see each event's JSON example below to check |
382| `hook_event_name` | Name of the event that fired |456| `hook_event_name` | Name of the event that fired |
383 457
458When running with `--agent` or inside a subagent, two additional fields are included:
459
460| Field | Description |
461| :----------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
462| `agent_id` | Unique identifier for the subagent. Present only when the hook fires inside a subagent call. Use this to distinguish subagent hook calls from main-thread calls. |
463| `agent_type` | Agent name (for example, `"Explore"` or `"security-reviewer"`). Present when the session uses `--agent` or the hook fires inside a subagent. For subagents, the subagent's type takes precedence over the session's `--agent` value. |
464
384For example, a `PreToolUse` hook for a Bash command receives this on stdin:465For example, a `PreToolUse` hook for a Bash command receives this on stdin:
385 466
386```json theme={null}467```json theme={null}
403 484
404The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.485The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.
405 486
406**Exit 0** means success. Claude Code parses stdout for [JSON output fields](#json-output) like `decision` or `reason`. JSON output is only processed on exit 0. For most events, stdout is only shown in verbose mode (`Ctrl+O`). The exceptions are `UserPromptSubmit` and `SessionStart`, where stdout is added as context that Claude can see and act on.487**Exit 0** means success. Claude Code parses stdout for [JSON output fields](#json-output). JSON output is only processed on exit 0. For most events, stdout is only shown in verbose mode (`Ctrl+O`). The exceptions are `UserPromptSubmit` and `SessionStart`, where stdout is added as context that Claude can see and act on.
407 488
408**Exit 2** means a blocking error. Claude Code ignores stdout and any JSON in it. Instead, stderr text is fed back to Claude as an error message. The effect depends on the event: `PreToolUse` blocks the tool call, `UserPromptSubmit` rejects the prompt, and so on. See [exit code 2 behavior](#exit-code-2-behavior-per-event) for the full list.489**Exit 2** means a blocking error. Claude Code ignores stdout and any JSON in it. Instead, stderr text is fed back to Claude as an error message. The effect depends on the event: `PreToolUse` blocks the tool call, `UserPromptSubmit` rejects the prompt, and so on. See [exit code 2 behavior](#exit-code-2-behavior-per-event) for the full list.
409 490
429Exit code 2 is the way a hook signals "stop, don't do this." The effect depends on the event, because some events represent actions that can be blocked (like a tool call that hasn't happened yet) and others represent things that already happened or can't be prevented.510Exit code 2 is the way a hook signals "stop, don't do this." The effect depends on the event, because some events represent actions that can be blocked (like a tool call that hasn't happened yet) and others represent things that already happened or can't be prevented.
430 511
431| Hook event | Can block? | What happens on exit 2 |512| Hook event | Can block? | What happens on exit 2 |
432| :------------------- | :--------- | :-------------------------------------------------------- |513| :------------------- | :--------- | :---------------------------------------------------------------------------- |
433| `PreToolUse` | Yes | Blocks the tool call |514| `PreToolUse` | Yes | Blocks the tool call |
434| `PermissionRequest` | Yes | Denies the permission |515| `PermissionRequest` | Yes | Denies the permission |
435| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |516| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |
436| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |517| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |
437| `SubagentStop` | Yes | Prevents the subagent from stopping |518| `SubagentStop` | Yes | Prevents the subagent from stopping |
519| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |
520| `TaskCompleted` | Yes | Prevents the task from being marked as completed |
521| `ConfigChange` | Yes | Blocks the configuration change from taking effect (except `policy_settings`) |
522| `StopFailure` | No | Output and exit code are ignored |
438| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |523| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |
439| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |524| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |
440| `Notification` | No | Shows stderr to user only |525| `Notification` | No | Shows stderr to user only |
442| `SessionStart` | No | Shows stderr to user only |527| `SessionStart` | No | Shows stderr to user only |
443| `SessionEnd` | No | Shows stderr to user only |528| `SessionEnd` | No | Shows stderr to user only |
444| `PreCompact` | No | Shows stderr to user only |529| `PreCompact` | No | Shows stderr to user only |
530| `PostCompact` | No | Shows stderr to user only |
531| `Elicitation` | Yes | Denies the elicitation |
532| `ElicitationResult` | Yes | Blocks the response (action becomes decline) |
533| `WorktreeCreate` | Yes | Any non-zero exit code causes worktree creation to fail |
534| `WorktreeRemove` | No | Failures are logged in debug mode only |
535| `InstructionsLoaded` | No | Exit code is ignored |
536
537### HTTP response handling
538
539HTTP hooks use HTTP status codes and response bodies instead of exit codes and stdout:
540
541* **2xx with an empty body**: success, equivalent to exit code 0 with no output
542* **2xx with a plain text body**: success, the text is added as context
543* **2xx with a JSON body**: success, parsed using the same [JSON output](#json-output) schema as command hooks
544* **Non-2xx status**: non-blocking error, execution continues
545* **Connection failure or timeout**: non-blocking error, execution continues
546
547Unlike command hooks, HTTP hooks cannot signal a blocking error through status codes alone. To block a tool call or deny a permission, return a 2xx response with a JSON body containing the appropriate decision fields.
445 548
446### JSON output549### JSON output
447 550
448You must choose one approach per hook, not both: either use exit codes alone for signaling, or exit 0 and print JSON for structured control. Claude Code only processes JSON on exit 0. If you exit 2, any JSON is ignored.551Exit codes let you allow or block, but JSON output gives you finer-grained control. Instead of exiting with code 2 to block, exit 0 and print a JSON object to stdout. Claude Code reads specific fields from that JSON to control behavior, including [decision control](#decision-control) for blocking, allowing, or escalating to the user.
449 552
450Instead of relying on exit codes alone, hooks can print JSON to stdout on exit 0. Claude Code reads specific fields from this JSON to decide what to do next.553<Note>
554 You must choose one approach per hook, not both: either use exit codes alone for signaling, or exit 0 and print JSON for structured control. Claude Code only processes JSON on exit 0. If you exit 2, any JSON is ignored.
555</Note>
451 556
452Your hook's stdout must contain only the JSON object. If your shell profile prints text on startup, it can interfere with JSON parsing. See [JSON validation failed](/en/hooks-guide#json-validation-failed) in the troubleshooting guide.557Your hook's stdout must contain only the JSON object. If your shell profile prints text on startup, it can interfere with JSON parsing. See [JSON validation failed](/en/hooks-guide#json-validation-failed) in the troubleshooting guide.
453 558
454The JSON object has two parts:559The JSON object supports three kinds of fields:
455 560
456* **Top-level fields** like `continue` and `decision` work across all events. These are listed in the table below.561* **Universal fields** like `continue` work across all events. These are listed in the table below.
457* **`hookSpecificOutput`** is a nested object for event-specific fields like `permissionDecision` or `additionalContext`. It requires a `hookEventName` field set to the event name, like `"PreToolUse"` or `"Stop"`. Each event's decision control section under [Hook events](#hook-events) documents what fields go here.562* **Top-level `decision` and `reason`** are used by some events to block or provide feedback.
563* **`hookSpecificOutput`** is a nested object for events that need richer control. It requires a `hookEventName` field set to the event name.
458 564
459| Field | Default | Description |565| Field | Default | Description |
460| :--------------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------- |566| :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------- |
461| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over event-specific fields like `decision` or `permissionDecision` |567| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over any event-specific decision fields |
462| `stopReason` | none | Message shown to the user when `continue` is `false`. Not shown to Claude |568| `stopReason` | none | Message shown to the user when `continue` is `false`. Not shown to Claude |
463| `suppressOutput` | `false` | If `true`, hides stdout from verbose mode output |569| `suppressOutput` | `false` | If `true`, hides stdout from verbose mode output |
464| `systemMessage` | none | Warning message shown to the user |570| `systemMessage` | none | Warning message shown to the user |
465 571
466This example uses a top-level field to stop Claude:572To stop Claude entirely regardless of event type:
467 573
468```json theme={null}574```json theme={null}
469{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }575{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
470```576```
471 577
472This example uses `hookSpecificOutput` to deny a PreToolUse tool call:578#### Decision control
473 579
474```json theme={null}580Not every event supports blocking or controlling behavior through JSON. The events that do each use a different set of fields to express that decision. Use this table as a quick reference before writing a hook:
475{581
582| Events | Decision pattern | Key fields |
583| :------------------------------------------------------------------------------------------------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
584| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange | Top-level `decision` | `decision: "block"`, `reason` |
585| TeammateIdle, TaskCompleted | Exit code or `continue: false` | Exit code 2 blocks the action with stderr feedback. JSON `{"continue": false, "stopReason": "..."}` also stops the teammate entirely, matching `Stop` hook behavior |
586| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask), `permissionDecisionReason` |
587| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |
588| WorktreeCreate | stdout path | Hook prints absolute path to created worktree. Non-zero exit fails creation |
589| Elicitation | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values for accept) |
590| ElicitationResult | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values override) |
591| WorktreeRemove, Notification, SessionEnd, PreCompact, PostCompact, InstructionsLoaded, StopFailure | None | No decision control. Used for side effects like logging or cleanup |
592
593Here are examples of each pattern in action:
594
595<Tabs>
596 <Tab title="Top-level decision">
597 Used by `UserPromptSubmit`, `PostToolUse`, `PostToolUseFailure`, `Stop`, `SubagentStop`, and `ConfigChange`. The only value is `"block"`. To allow the action to proceed, omit `decision` from your JSON, or exit 0 without any JSON at all:
598
599 ```json theme={null}
600 {
601 "decision": "block",
602 "reason": "Test suite must pass before proceeding"
603 }
604 ```
605 </Tab>
606
607 <Tab title="PreToolUse">
608 Uses `hookSpecificOutput` for richer control: allow, deny, or escalate to the user. You can also modify tool input before it runs or inject additional context for Claude. See [PreToolUse decision control](#pretooluse-decision-control) for the full set of options.
609
610 ```json theme={null}
611 {
476 "hookSpecificOutput": {612 "hookSpecificOutput": {
477 "hookEventName": "PreToolUse",613 "hookEventName": "PreToolUse",
478 "permissionDecision": "deny",614 "permissionDecision": "deny",
479 "permissionDecisionReason": "Database writes are not allowed"615 "permissionDecisionReason": "Database writes are not allowed"
480 }616 }
481}617 }
482```618 ```
619 </Tab>
620
621 <Tab title="PermissionRequest">
622 Uses `hookSpecificOutput` to allow or deny a permission request on behalf of the user. When allowing, you can also modify the tool's input or apply permission rules so the user isn't prompted again. See [PermissionRequest decision control](#permissionrequest-decision-control) for the full set of options.
623
624 ```json theme={null}
625 {
626 "hookSpecificOutput": {
627 "hookEventName": "PermissionRequest",
628 "decision": {
629 "behavior": "allow",
630 "updatedInput": {
631 "command": "npm run lint"
632 }
633 }
634 }
635 }
636 ```
637 </Tab>
638</Tabs>
483 639
484For extended examples including Bash command validation, prompt filtering, and auto-approval scripts, see [What you can automate](/en/hooks-guide#what-you-can-automate) in the guide and the [Bash command validator reference implementation](https://github.com/anthropics/claude-code/blob/main/examples/hooks/bash_command_validator_example.py).640For extended examples including Bash command validation, prompt filtering, and auto-approval scripts, see [What you can automate](/en/hooks-guide#what-you-can-automate) in the guide and the [Bash command validator reference implementation](https://github.com/anthropics/claude-code/blob/main/examples/hooks/bash_command_validator_example.py).
485 641
491 647
492Runs when Claude Code starts a new session or resumes an existing session. Useful for loading development context like existing issues or recent changes to your codebase, or setting up environment variables. For static context that does not require a script, use [CLAUDE.md](/en/memory) instead.648Runs when Claude Code starts a new session or resumes an existing session. Useful for loading development context like existing issues or recent changes to your codebase, or setting up environment variables. For static context that does not require a script, use [CLAUDE.md](/en/memory) instead.
493 649
494SessionStart runs on every session, so keep these hooks fast.650SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` hooks are supported.
495 651
496The matcher value corresponds to how the session was initiated:652The matcher value corresponds to how the session was initiated:
497 653
511 "session_id": "abc123",667 "session_id": "abc123",
512 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",668 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
513 "cwd": "/Users/...",669 "cwd": "/Users/...",
514 "permission_mode": "default",
515 "hook_event_name": "SessionStart",670 "hook_event_name": "SessionStart",
516 "source": "startup",671 "source": "startup",
517 "model": "claude-sonnet-4-5-20250929"672 "model": "claude-sonnet-4-6"
518}673}
519```674```
520 675
578 `CLAUDE_ENV_FILE` is available for SessionStart hooks. Other hook types do not have access to this variable.733 `CLAUDE_ENV_FILE` is available for SessionStart hooks. Other hook types do not have access to this variable.
579</Note>734</Note>
580 735
736### InstructionsLoaded
737
738Fires when a `CLAUDE.md` or `.claude/rules/*.md` file is loaded into context. This event fires at session start for eagerly-loaded files and again later when files are lazily loaded, for example when Claude accesses a subdirectory that contains a nested `CLAUDE.md` or when conditional rules with `paths:` frontmatter match. The hook does not support blocking or decision control. It runs asynchronously for observability purposes.
739
740The matcher runs against `load_reason`. For example, use `"matcher": "session_start"` to fire only for files loaded at session start, or `"matcher": "path_glob_match|nested_traversal"` to fire only for lazy loads.
741
742#### InstructionsLoaded input
743
744In addition to the [common input fields](#common-input-fields), InstructionsLoaded hooks receive these fields:
745
746| Field | Description |
747| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
748| `file_path` | Absolute path to the instruction file that was loaded |
749| `memory_type` | Scope of the file: `"User"`, `"Project"`, `"Local"`, or `"Managed"` |
750| `load_reason` | Why the file was loaded: `"session_start"`, `"nested_traversal"`, `"path_glob_match"`, `"include"`, or `"compact"`. The `"compact"` value fires when instruction files are re-loaded after a compaction event |
751| `globs` | Path glob patterns from the file's `paths:` frontmatter, if any. Present only for `path_glob_match` loads |
752| `trigger_file_path` | Path to the file whose access triggered this load, for lazy loads |
753| `parent_file_path` | Path to the parent instruction file that included this one, for `include` loads |
754
755```json theme={null}
756{
757 "session_id": "abc123",
758 "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
759 "cwd": "/Users/my-project",
760 "hook_event_name": "InstructionsLoaded",
761 "file_path": "/Users/my-project/CLAUDE.md",
762 "memory_type": "Project",
763 "load_reason": "session_start"
764}
765```
766
767#### InstructionsLoaded decision control
768
769InstructionsLoaded hooks have no decision control. They cannot block or modify instruction loading. Use this event for audit logging, compliance tracking, or observability.
770
581### UserPromptSubmit771### UserPromptSubmit
582 772
583Runs when the user submits a prompt, before Claude processes it. This allows you773Runs when the user submits a prompt, before Claude processes it. This allows you
636 826
637### PreToolUse827### PreToolUse
638 828
639Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name: `Bash`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Task`, `WebFetch`, `WebSearch`, and any [MCP tool names](#match-mcp-tools).829Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name: `Bash`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Agent`, `WebFetch`, `WebSearch`, and any [MCP tool names](#match-mcp-tools).
640 830
641Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.831Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.
642 832
726| `allowed_domains` | array | `["docs.example.com"]` | Optional: only include results from these domains |916| `allowed_domains` | array | `["docs.example.com"]` | Optional: only include results from these domains |
727| `blocked_domains` | array | `["spam.example.com"]` | Optional: exclude results from these domains |917| `blocked_domains` | array | `["spam.example.com"]` | Optional: exclude results from these domains |
728 918
729##### Task919##### Agent
730 920
731Spawns a [subagent](/en/sub-agents).921Spawns a [subagent](/en/sub-agents).
732 922
739 929
740#### PreToolUse decision control930#### PreToolUse decision control
741 931
742`PreToolUse` hooks can control whether a tool call proceeds. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return a `hookSpecificOutput` object with these event-specific fields:932`PreToolUse` hooks can control whether a tool call proceeds. Unlike other hooks that use a top-level `decision` field, PreToolUse returns its decision inside a `hookSpecificOutput` object. This gives it richer control: three outcomes (allow, deny, or ask) plus the ability to modify tool input before execution.
743 933
744| Field | Description |934| Field | Description |
745| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |935| :------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
746| `permissionDecision` | `"allow"` bypasses the permission system, `"deny"` prevents the tool call, `"ask"` prompts the user to confirm |936| `permissionDecision` | `"allow"` skips the permission prompt. `"deny"` prevents the tool call. `"ask"` prompts the user to confirm. [Deny and ask rules](/en/permissions#manage-permissions) still apply when a hook returns `"allow"` |
747| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude |937| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude |
748| `updatedInput` | Modifies the tool's input parameters before execution. Combine with `"allow"` to auto-approve, or `"ask"` to show the modified input to the user |938| `updatedInput` | Modifies the tool's input parameters before execution. Combine with `"allow"` to auto-approve, or `"ask"` to show the modified input to the user |
749| `additionalContext` | String added to Claude's context before the tool executes |939| `additionalContext` | String added to Claude's context before the tool executes |
750 940
941When a hook returns `"ask"`, the permission prompt displayed to the user includes a label identifying where the hook came from: for example, `[User]`, `[Project]`, `[Plugin]`, or `[Local]`. This helps users understand which configuration source is requesting confirmation.
942
751```json theme={null}943```json theme={null}
752{944{
753 "hookSpecificOutput": {945 "hookSpecificOutput": {
763```955```
764 956
765<Note>957<Note>
766 The `decision` and `reason` fields are deprecated for PreToolUse hooks.958 PreToolUse previously used top-level `decision` and `reason` fields, but these are deprecated for this event. Use `hookSpecificOutput.permissionDecision` and `hookSpecificOutput.permissionDecisionReason` instead. The deprecated values `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively. Other events like PostToolUse and Stop continue to use top-level `decision` and `reason` as their current format.
767 Use `hookSpecificOutput.permissionDecision` and
768 `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields
769 `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.
770</Note>959</Note>
771 960
772### PermissionRequest961### PermissionRequest
793 "description": "Remove node_modules directory"982 "description": "Remove node_modules directory"
794 },983 },
795 "permission_suggestions": [984 "permission_suggestions": [
796 { "type": "toolAlwaysAllow", "tool": "Bash" }985 {
986 "type": "addRules",
987 "rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
988 "behavior": "allow",
989 "destination": "localSettings"
990 }
797 ]991 ]
798}992}
799```993```
803`PermissionRequest` hooks can allow or deny permission requests. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return a `decision` object with these event-specific fields:997`PermissionRequest` hooks can allow or deny permission requests. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return a `decision` object with these event-specific fields:
804 998
805| Field | Description |999| Field | Description |
806| :------------------- | :------------------------------------------------------------------------------------------------------------- |1000| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
807| `behavior` | `"allow"` grants the permission, `"deny"` denies it |1001| `behavior` | `"allow"` grants the permission, `"deny"` denies it |
808| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution |1002| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution |
809| `updatedPermissions` | For `"allow"` only: applies permission rule updates, equivalent to the user selecting an "always allow" option |1003| `updatedPermissions` | For `"allow"` only: array of [permission update entries](#permission-update-entries) to apply, such as adding an allow rule or changing the session permission mode |
810| `message` | For `"deny"` only: tells Claude why the permission was denied |1004| `message` | For `"deny"` only: tells Claude why the permission was denied |
811| `interrupt` | For `"deny"` only: if `true`, stops Claude |1005| `interrupt` | For `"deny"` only: if `true`, stops Claude |
812 1006
824}1018}
825```1019```
826 1020
1021#### Permission update entries
1022
1023The `updatedPermissions` output field and the [`permission_suggestions` input field](#permissionrequest-input) both use the same array of entry objects. Each entry has a `type` that determines its other fields, and a `destination` that controls where the change is written.
1024
1025| `type` | Fields | Effect |
1026| :------------------ | :--------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1027| `addRules` | `rules`, `behavior`, `destination` | Adds permission rules. `rules` is an array of `{toolName, ruleContent?}` objects. Omit `ruleContent` to match the whole tool. `behavior` is `"allow"`, `"deny"`, or `"ask"` |
1028| `replaceRules` | `rules`, `behavior`, `destination` | Replaces all rules of the given `behavior` at the `destination` with the provided `rules` |
1029| `removeRules` | `rules`, `behavior`, `destination` | Removes matching rules of the given `behavior` |
1030| `setMode` | `mode`, `destination` | Changes the permission mode. Valid modes are `default`, `acceptEdits`, `dontAsk`, `bypassPermissions`, and `plan` |
1031| `addDirectories` | `directories`, `destination` | Adds working directories. `directories` is an array of path strings |
1032| `removeDirectories` | `directories`, `destination` | Removes working directories |
1033
1034The `destination` field on every entry determines whether the change stays in memory or persists to a settings file.
1035
1036| `destination` | Writes to |
1037| :---------------- | :---------------------------------------------- |
1038| `session` | in-memory only, discarded when the session ends |
1039| `localSettings` | `.claude/settings.local.json` |
1040| `projectSettings` | `.claude/settings.json` |
1041| `userSettings` | `~/.claude/settings.json` |
1042
1043A hook can echo one of the `permission_suggestions` it received as its own `updatedPermissions` output, which is equivalent to the user selecting that "always allow" option in the dialog.
1044
827### PostToolUse1045### PostToolUse
828 1046
829Runs immediately after a tool completes successfully.1047Runs immediately after a tool completes successfully.
968 "session_id": "abc123",1186 "session_id": "abc123",
969 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1187 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
970 "cwd": "/Users/...",1188 "cwd": "/Users/...",
971 "permission_mode": "default",
972 "hook_event_name": "Notification",1189 "hook_event_name": "Notification",
973 "message": "Claude needs your permission to use Bash",1190 "message": "Claude needs your permission to use Bash",
974 "title": "Permission needed",1191 "title": "Permission needed",
984 1201
985### SubagentStart1202### SubagentStart
986 1203
987Runs when a Claude Code subagent is spawned via the Task tool. Supports matchers to filter by agent type name (built-in agents like `Bash`, `Explore`, `Plan`, or custom agent names from `.claude/agents/`).1204Runs when a Claude Code subagent is spawned via the Agent tool. Supports matchers to filter by agent type name (built-in agents like `Bash`, `Explore`, `Plan`, or custom agent names from `.claude/agents/`).
988 1205
989#### SubagentStart input1206#### SubagentStart input
990 1207
995 "session_id": "abc123",1212 "session_id": "abc123",
996 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1213 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
997 "cwd": "/Users/...",1214 "cwd": "/Users/...",
998 "permission_mode": "default",
999 "hook_event_name": "SubagentStart",1215 "hook_event_name": "SubagentStart",
1000 "agent_id": "agent-abc123",1216 "agent_id": "agent-abc123",
1001 "agent_type": "Explore"1217 "agent_type": "Explore"
1023 1239
1024#### SubagentStop input1240#### SubagentStop input
1025 1241
1026In addition to the [common input fields](#common-input-fields), SubagentStop hooks receive `stop_hook_active`, `agent_id`, `agent_type`, and `agent_transcript_path`. The `agent_type` field is the value used for matcher filtering. The `transcript_path` is the main session's transcript, while `agent_transcript_path` is the subagent's own transcript stored in a nested `subagents/` folder.1242In addition to the [common input fields](#common-input-fields), SubagentStop hooks receive `stop_hook_active`, `agent_id`, `agent_type`, `agent_transcript_path`, and `last_assistant_message`. The `agent_type` field is the value used for matcher filtering. The `transcript_path` is the main session's transcript, while `agent_transcript_path` is the subagent's own transcript stored in a nested `subagents/` folder. The `last_assistant_message` field contains the text content of the subagent's final response, so hooks can access it without parsing the transcript file.
1027 1243
1028```json theme={null}1244```json theme={null}
1029{1245{
1035 "stop_hook_active": false,1251 "stop_hook_active": false,
1036 "agent_id": "def456",1252 "agent_id": "def456",
1037 "agent_type": "Explore",1253 "agent_type": "Explore",
1038 "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl"1254 "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
1255 "last_assistant_message": "Analysis complete. Found 3 potential issues..."
1039}1256}
1040```1257```
1041 1258
1044### Stop1261### Stop
1045 1262
1046Runs when the main Claude Code agent has finished responding. Does not run if1263Runs when the main Claude Code agent has finished responding. Does not run if
1047the stoppage occurred due to a user interrupt.1264the stoppage occurred due to a user interrupt. API errors fire
1265[StopFailure](#stopfailure) instead.
1048 1266
1049#### Stop input1267#### Stop input
1050 1268
1051In addition to the [common input fields](#common-input-fields), Stop hooks receive `stop_hook_active`. This field is `true` when Claude Code is already continuing as a result of a stop hook. Check this value or process the transcript to prevent Claude Code from running indefinitely.1269In addition to the [common input fields](#common-input-fields), Stop hooks receive `stop_hook_active` and `last_assistant_message`. The `stop_hook_active` field is `true` when Claude Code is already continuing as a result of a stop hook. Check this value or process the transcript to prevent Claude Code from running indefinitely. The `last_assistant_message` field contains the text content of Claude's final response, so hooks can access it without parsing the transcript file.
1052 1270
1053```json theme={null}1271```json theme={null}
1054{1272{
1057 "cwd": "/Users/...",1275 "cwd": "/Users/...",
1058 "permission_mode": "default",1276 "permission_mode": "default",
1059 "hook_event_name": "Stop",1277 "hook_event_name": "Stop",
1060 "stop_hook_active": true1278 "stop_hook_active": true,
1279 "last_assistant_message": "I've completed the refactoring. Here's a summary..."
1061}1280}
1062```1281```
1063 1282
1077}1296}
1078```1297```
1079 1298
1299### StopFailure
1300
1301Runs instead of [Stop](#stop) when the turn ends due to an API error. Output and exit code are ignored. Use this to log failures, send alerts, or take recovery actions when Claude cannot complete a response due to rate limits, authentication problems, or other API errors.
1302
1303#### StopFailure input
1304
1305In addition to the [common input fields](#common-input-fields), StopFailure hooks receive `error`, optional `error_details`, and optional `last_assistant_message`. The `error` field identifies the error type and is used for matcher filtering.
1306
1307| Field | Description |
1308| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1309| `error` | Error type: `rate_limit`, `authentication_failed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, or `unknown` |
1310| `error_details` | Additional details about the error, when available |
1311| `last_assistant_message` | The rendered error text shown in the conversation. Unlike `Stop` and `SubagentStop`, where this field holds Claude's conversational output, for `StopFailure` it contains the API error string itself, such as `"API Error: Rate limit reached"` |
1312
1313```json theme={null}
1314{
1315 "session_id": "abc123",
1316 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1317 "cwd": "/Users/...",
1318 "hook_event_name": "StopFailure",
1319 "error": "rate_limit",
1320 "error_details": "429 Too Many Requests",
1321 "last_assistant_message": "API Error: Rate limit reached"
1322}
1323```
1324
1325StopFailure hooks have no decision control. They run for notification and logging purposes only.
1326
1327### TeammateIdle
1328
1329Runs when an [agent team](/en/agent-teams) teammate is about to go idle after finishing its turn. Use this to enforce quality gates before a teammate stops working, such as requiring passing lint checks or verifying that output files exist.
1330
1331When a `TeammateIdle` hook exits with code 2, the teammate receives the stderr message as feedback and continues working instead of going idle. To stop the teammate entirely instead of re-running it, return JSON with `{"continue": false, "stopReason": "..."}`. TeammateIdle hooks do not support matchers and fire on every occurrence.
1332
1333#### TeammateIdle input
1334
1335In addition to the [common input fields](#common-input-fields), TeammateIdle hooks receive `teammate_name` and `team_name`.
1336
1337```json theme={null}
1338{
1339 "session_id": "abc123",
1340 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1341 "cwd": "/Users/...",
1342 "permission_mode": "default",
1343 "hook_event_name": "TeammateIdle",
1344 "teammate_name": "researcher",
1345 "team_name": "my-project"
1346}
1347```
1348
1349| Field | Description |
1350| :-------------- | :-------------------------------------------- |
1351| `teammate_name` | Name of the teammate that is about to go idle |
1352| `team_name` | Name of the team |
1353
1354#### TeammateIdle decision control
1355
1356TeammateIdle hooks support two ways to control teammate behavior:
1357
1358* **Exit code 2**: the teammate receives the stderr message as feedback and continues working instead of going idle.
1359* **JSON `{"continue": false, "stopReason": "..."}`**: stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user.
1360
1361This example checks that a build artifact exists before allowing a teammate to go idle:
1362
1363```bash theme={null}
1364#!/bin/bash
1365
1366if [ ! -f "./dist/output.js" ]; then
1367 echo "Build artifact missing. Run the build before stopping." >&2
1368 exit 2
1369fi
1370
1371exit 0
1372```
1373
1374### TaskCompleted
1375
1376Runs when a task is being marked as completed. This fires in two situations: when any agent explicitly marks a task as completed through the TaskUpdate tool, or when an [agent team](/en/agent-teams) teammate finishes its turn with in-progress tasks. Use this to enforce completion criteria like passing tests or lint checks before a task can close.
1377
1378When a `TaskCompleted` hook exits with code 2, the task is not marked as completed and the stderr message is fed back to the model as feedback. To stop the teammate entirely instead of re-running it, return JSON with `{"continue": false, "stopReason": "..."}`. TaskCompleted hooks do not support matchers and fire on every occurrence.
1379
1380#### TaskCompleted input
1381
1382In addition to the [common input fields](#common-input-fields), TaskCompleted hooks receive `task_id`, `task_subject`, and optionally `task_description`, `teammate_name`, and `team_name`.
1383
1384```json theme={null}
1385{
1386 "session_id": "abc123",
1387 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1388 "cwd": "/Users/...",
1389 "permission_mode": "default",
1390 "hook_event_name": "TaskCompleted",
1391 "task_id": "task-001",
1392 "task_subject": "Implement user authentication",
1393 "task_description": "Add login and signup endpoints",
1394 "teammate_name": "implementer",
1395 "team_name": "my-project"
1396}
1397```
1398
1399| Field | Description |
1400| :----------------- | :------------------------------------------------------ |
1401| `task_id` | Identifier of the task being completed |
1402| `task_subject` | Title of the task |
1403| `task_description` | Detailed description of the task. May be absent |
1404| `teammate_name` | Name of the teammate completing the task. May be absent |
1405| `team_name` | Name of the team. May be absent |
1406
1407#### TaskCompleted decision control
1408
1409TaskCompleted hooks support two ways to control task completion:
1410
1411* **Exit code 2**: the task is not marked as completed and the stderr message is fed back to the model as feedback.
1412* **JSON `{"continue": false, "stopReason": "..."}`**: stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user.
1413
1414This example runs tests and blocks task completion if they fail:
1415
1416```bash theme={null}
1417#!/bin/bash
1418INPUT=$(cat)
1419TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
1420
1421# Run the test suite
1422if ! npm test 2>&1; then
1423 echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
1424 exit 2
1425fi
1426
1427exit 0
1428```
1429
1430### ConfigChange
1431
1432Runs when a configuration file changes during a session. Use this to audit settings changes, enforce security policies, or block unauthorized modifications to configuration files.
1433
1434ConfigChange hooks fire for changes to settings files, managed policy settings, and skill files. The `source` field in the input tells you which type of configuration changed, and the optional `file_path` field provides the path to the changed file.
1435
1436The matcher filters on the configuration source:
1437
1438| Matcher | When it fires |
1439| :----------------- | :---------------------------------------- |
1440| `user_settings` | `~/.claude/settings.json` changes |
1441| `project_settings` | `.claude/settings.json` changes |
1442| `local_settings` | `.claude/settings.local.json` changes |
1443| `policy_settings` | Managed policy settings change |
1444| `skills` | A skill file in `.claude/skills/` changes |
1445
1446This example logs all configuration changes for security auditing:
1447
1448```json theme={null}
1449{
1450 "hooks": {
1451 "ConfigChange": [
1452 {
1453 "hooks": [
1454 {
1455 "type": "command",
1456 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit-config-change.sh"
1457 }
1458 ]
1459 }
1460 ]
1461 }
1462}
1463```
1464
1465#### ConfigChange input
1466
1467In addition to the [common input fields](#common-input-fields), ConfigChange hooks receive `source` and optionally `file_path`. The `source` field indicates which configuration type changed, and `file_path` provides the path to the specific file that was modified.
1468
1469```json theme={null}
1470{
1471 "session_id": "abc123",
1472 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1473 "cwd": "/Users/...",
1474 "hook_event_name": "ConfigChange",
1475 "source": "project_settings",
1476 "file_path": "/Users/.../my-project/.claude/settings.json"
1477}
1478```
1479
1480#### ConfigChange decision control
1481
1482ConfigChange hooks can block configuration changes from taking effect. Use exit code 2 or a JSON `decision` to prevent the change. When blocked, the new settings are not applied to the running session.
1483
1484| Field | Description |
1485| :--------- | :--------------------------------------------------------------------------------------- |
1486| `decision` | `"block"` prevents the configuration change from being applied. Omit to allow the change |
1487| `reason` | Explanation shown to the user when `decision` is `"block"` |
1488
1489```json theme={null}
1490{
1491 "decision": "block",
1492 "reason": "Configuration changes to project settings require admin approval"
1493}
1494```
1495
1496`policy_settings` changes cannot be blocked. Hooks still fire for `policy_settings` sources, so you can use them for audit logging, but any blocking decision is ignored. This ensures enterprise-managed settings always take effect.
1497
1498### WorktreeCreate
1499
1500When you run `claude --worktree` or a [subagent uses `isolation: "worktree"`](/en/sub-agents#choose-the-subagent-scope), Claude Code creates an isolated working copy using `git worktree`. If you configure a WorktreeCreate hook, it replaces the default git behavior, letting you use a different version control system like SVN, Perforce, or Mercurial.
1501
1502The hook must print the absolute path to the created worktree directory on stdout. Claude Code uses this path as the working directory for the isolated session.
1503
1504This example creates an SVN working copy and prints the path for Claude Code to use. Replace the repository URL with your own:
1505
1506```json theme={null}
1507{
1508 "hooks": {
1509 "WorktreeCreate": [
1510 {
1511 "hooks": [
1512 {
1513 "type": "command",
1514 "command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
1515 }
1516 ]
1517 }
1518 ]
1519 }
1520}
1521```
1522
1523The hook reads the worktree `name` from the JSON input on stdin, checks out a fresh copy into a new directory, and prints the directory path. The `echo` on the last line is what Claude Code reads as the worktree path. Redirect any other output to stderr so it doesn't interfere with the path.
1524
1525#### WorktreeCreate input
1526
1527In addition to the [common input fields](#common-input-fields), WorktreeCreate hooks receive the `name` field. This is a slug identifier for the new worktree, either specified by the user or auto-generated (for example, `bold-oak-a3f2`).
1528
1529```json theme={null}
1530{
1531 "session_id": "abc123",
1532 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1533 "cwd": "/Users/...",
1534 "hook_event_name": "WorktreeCreate",
1535 "name": "feature-auth"
1536}
1537```
1538
1539#### WorktreeCreate output
1540
1541The hook must print the absolute path to the created worktree directory on stdout. If the hook fails or produces no output, worktree creation fails with an error.
1542
1543WorktreeCreate hooks do not use the standard allow/block decision model. Instead, the hook's success or failure determines the outcome. Only `type: "command"` hooks are supported.
1544
1545### WorktreeRemove
1546
1547The cleanup counterpart to [WorktreeCreate](#worktreecreate). This hook fires when a worktree is being removed, either when you exit a `--worktree` session and choose to remove it, or when a subagent with `isolation: "worktree"` finishes. For git-based worktrees, Claude handles cleanup automatically with `git worktree remove`. If you configured a WorktreeCreate hook for a non-git version control system, pair it with a WorktreeRemove hook to handle cleanup. Without one, the worktree directory is left on disk.
1548
1549Claude Code passes the path that WorktreeCreate printed on stdout as `worktree_path` in the hook input. This example reads that path and removes the directory:
1550
1551```json theme={null}
1552{
1553 "hooks": {
1554 "WorktreeRemove": [
1555 {
1556 "hooks": [
1557 {
1558 "type": "command",
1559 "command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
1560 }
1561 ]
1562 }
1563 ]
1564 }
1565}
1566```
1567
1568#### WorktreeRemove input
1569
1570In addition to the [common input fields](#common-input-fields), WorktreeRemove hooks receive the `worktree_path` field, which is the absolute path to the worktree being removed.
1571
1572```json theme={null}
1573{
1574 "session_id": "abc123",
1575 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1576 "cwd": "/Users/...",
1577 "hook_event_name": "WorktreeRemove",
1578 "worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
1579}
1580```
1581
1582WorktreeRemove hooks have no decision control. They cannot block worktree removal but can perform cleanup tasks like removing version control state or archiving changes. Hook failures are logged in debug mode only. Only `type: "command"` hooks are supported.
1583
1080### PreCompact1584### PreCompact
1081 1585
1082Runs before Claude Code is about to run a compact operation.1586Runs before Claude Code is about to run a compact operation.
1097 "session_id": "abc123",1601 "session_id": "abc123",
1098 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1602 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1099 "cwd": "/Users/...",1603 "cwd": "/Users/...",
1100 "permission_mode": "default",
1101 "hook_event_name": "PreCompact",1604 "hook_event_name": "PreCompact",
1102 "trigger": "manual",1605 "trigger": "manual",
1103 "custom_instructions": ""1606 "custom_instructions": ""
1104}1607}
1105```1608```
1106 1609
1610### PostCompact
1611
1612Runs after Claude Code completes a compact operation. Use this event to react to the new compacted state, for example to log the generated summary or update external state.
1613
1614The same matcher values apply as for `PreCompact`:
1615
1616| Matcher | When it fires |
1617| :------- | :------------------------------------------------- |
1618| `manual` | After `/compact` |
1619| `auto` | After auto-compact when the context window is full |
1620
1621#### PostCompact input
1622
1623In addition to the [common input fields](#common-input-fields), PostCompact hooks receive `trigger` and `compact_summary`. The `compact_summary` field contains the conversation summary generated by the compact operation.
1624
1625```json theme={null}
1626{
1627 "session_id": "abc123",
1628 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1629 "cwd": "/Users/...",
1630 "hook_event_name": "PostCompact",
1631 "trigger": "manual",
1632 "compact_summary": "Summary of the compacted conversation..."
1633}
1634```
1635
1636PostCompact hooks have no decision control. They cannot affect the compaction result but can perform follow-up tasks.
1637
1107### SessionEnd1638### SessionEnd
1108 1639
1109Runs when a Claude Code session ends. Useful for cleanup tasks, logging session1640Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
1114| Reason | Description |1645| Reason | Description |
1115| :---------------------------- | :----------------------------------------- |1646| :---------------------------- | :----------------------------------------- |
1116| `clear` | Session cleared with `/clear` command |1647| `clear` | Session cleared with `/clear` command |
1648| `resume` | Session switched via interactive `/resume` |
1117| `logout` | User logged out |1649| `logout` | User logged out |
1118| `prompt_input_exit` | User exited while prompt input was visible |1650| `prompt_input_exit` | User exited while prompt input was visible |
1119| `bypass_permissions_disabled` | Bypass permissions mode was disabled |1651| `bypass_permissions_disabled` | Bypass permissions mode was disabled |
1128 "session_id": "abc123",1660 "session_id": "abc123",
1129 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1661 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1130 "cwd": "/Users/...",1662 "cwd": "/Users/...",
1131 "permission_mode": "default",
1132 "hook_event_name": "SessionEnd",1663 "hook_event_name": "SessionEnd",
1133 "reason": "other"1664 "reason": "other"
1134}1665}
1136 1667
1137SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.1668SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.
1138 1669
1670SessionEnd hooks have a default timeout of 1.5 seconds. This applies to session exit, `/clear`, and switching sessions via interactive `/resume`. If your hooks need more time, set the `CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS` environment variable to a higher value in milliseconds. Any per-hook `timeout` setting is also capped by this value.
1671
1672```bash theme={null}
1673CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude
1674```
1675
1676### Elicitation
1677
1678Runs when an MCP server requests user input mid-task. By default, Claude Code shows an interactive dialog for the user to respond. Hooks can intercept this request and respond programmatically, skipping the dialog entirely.
1679
1680The matcher field matches against the MCP server name.
1681
1682#### Elicitation input
1683
1684In addition to the [common input fields](#common-input-fields), Elicitation hooks receive `mcp_server_name`, `message`, and optional `mode`, `url`, `elicitation_id`, and `requested_schema` fields.
1685
1686For form-mode elicitation (the most common case):
1687
1688```json theme={null}
1689{
1690 "session_id": "abc123",
1691 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1692 "cwd": "/Users/...",
1693 "permission_mode": "default",
1694 "hook_event_name": "Elicitation",
1695 "mcp_server_name": "my-mcp-server",
1696 "message": "Please provide your credentials",
1697 "mode": "form",
1698 "requested_schema": {
1699 "type": "object",
1700 "properties": {
1701 "username": { "type": "string", "title": "Username" }
1702 }
1703 }
1704}
1705```
1706
1707For URL-mode elicitation (browser-based authentication):
1708
1709```json theme={null}
1710{
1711 "session_id": "abc123",
1712 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1713 "cwd": "/Users/...",
1714 "permission_mode": "default",
1715 "hook_event_name": "Elicitation",
1716 "mcp_server_name": "my-mcp-server",
1717 "message": "Please authenticate",
1718 "mode": "url",
1719 "url": "https://auth.example.com/login"
1720}
1721```
1722
1723#### Elicitation output
1724
1725To respond programmatically without showing the dialog, return a JSON object with `hookSpecificOutput`:
1726
1727```json theme={null}
1728{
1729 "hookSpecificOutput": {
1730 "hookEventName": "Elicitation",
1731 "action": "accept",
1732 "content": {
1733 "username": "alice"
1734 }
1735 }
1736}
1737```
1738
1739| Field | Values | Description |
1740| :-------- | :---------------------------- | :--------------------------------------------------------------- |
1741| `action` | `accept`, `decline`, `cancel` | Whether to accept, decline, or cancel the request |
1742| `content` | object | Form field values to submit. Only used when `action` is `accept` |
1743
1744Exit code 2 denies the elicitation and shows stderr to the user.
1745
1746### ElicitationResult
1747
1748Runs after a user responds to an MCP elicitation. Hooks can observe, modify, or block the response before it is sent back to the MCP server.
1749
1750The matcher field matches against the MCP server name.
1751
1752#### ElicitationResult input
1753
1754In addition to the [common input fields](#common-input-fields), ElicitationResult hooks receive `mcp_server_name`, `action`, and optional `mode`, `elicitation_id`, and `content` fields.
1755
1756```json theme={null}
1757{
1758 "session_id": "abc123",
1759 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1760 "cwd": "/Users/...",
1761 "permission_mode": "default",
1762 "hook_event_name": "ElicitationResult",
1763 "mcp_server_name": "my-mcp-server",
1764 "action": "accept",
1765 "content": { "username": "alice" },
1766 "mode": "form",
1767 "elicitation_id": "elicit-123"
1768}
1769```
1770
1771#### ElicitationResult output
1772
1773To override the user's response, return a JSON object with `hookSpecificOutput`:
1774
1775```json theme={null}
1776{
1777 "hookSpecificOutput": {
1778 "hookEventName": "ElicitationResult",
1779 "action": "decline",
1780 "content": {}
1781 }
1782}
1783```
1784
1785| Field | Values | Description |
1786| :-------- | :---------------------------- | :--------------------------------------------------------------------- |
1787| `action` | `accept`, `decline`, `cancel` | Overrides the user's action |
1788| `content` | object | Overrides form field values. Only meaningful when `action` is `accept` |
1789
1790Exit code 2 blocks the response, changing the effective action to `decline`.
1791
1139## Prompt-based hooks1792## Prompt-based hooks
1140 1793
1141In addition to Bash command hooks (`type: "command"`), Claude Code supports prompt-based hooks (`type: "prompt"`) that use an LLM to evaluate whether to allow or block an action. Prompt-based hooks work with the following events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `UserPromptSubmit`, `Stop`, and `SubagentStop`.1794In addition to command and HTTP hooks, Claude Code supports prompt-based hooks (`type: "prompt"`) that use an LLM to evaluate whether to allow or block an action, and agent hooks (`type: "agent"`) that spawn an agentic verifier with tool access. Not all events support every hook type.
1795
1796Events that support all four hook types (`command`, `http`, `prompt`, and `agent`):
1797
1798* `PermissionRequest`
1799* `PostToolUse`
1800* `PostToolUseFailure`
1801* `PreToolUse`
1802* `Stop`
1803* `SubagentStop`
1804* `TaskCompleted`
1805* `UserPromptSubmit`
1806
1807Events that only support `type: "command"` hooks:
1808
1809* `ConfigChange`
1810* `Elicitation`
1811* `ElicitationResult`
1812* `InstructionsLoaded`
1813* `Notification`
1814* `PostCompact`
1815* `PreCompact`
1816* `SessionEnd`
1817* `SessionStart`
1818* `StopFailure`
1819* `SubagentStart`
1820* `TeammateIdle`
1821* `WorktreeCreate`
1822* `WorktreeRemove`
1142 1823
1143### How prompt-based hooks work1824### How prompt-based hooks work
1144 1825
1302 1983
1303After the background process exits, if the hook produced a JSON response with a `systemMessage` or `additionalContext` field, that content is delivered to Claude as context on the next conversation turn.1984After the background process exits, if the hook produced a JSON response with a `systemMessage` or `additionalContext` field, that content is delivered to Claude as context on the next conversation turn.
1304 1985
1986Async hook completion notifications are suppressed by default. To see them, enable verbose mode with `Ctrl+O` or start Claude Code with `--verbose`.
1987
1305### Example: run tests after file changes1988### Example: run tests after file changes
1306 1989
1307This hook starts a test suite in the background whenever Claude writes a file, then reports the results back to Claude when the tests finish. Save this script to `.claude/hooks/run-tests-async.sh` in your project and make it executable with `chmod +x`:1990This hook starts a test suite in the background whenever Claude writes a file, then reports the results back to Claude when the tests finish. Save this script to `.claude/hooks/run-tests-async.sh` in your project and make it executable with `chmod +x`:
1365 2048
1366### Disclaimer2049### Disclaimer
1367 2050
1368Hooks run with your system user's full permissions.2051Command hooks run with your system user's full permissions.
1369 2052
1370<Warning>2053<Warning>
1371 Hooks execute shell commands with your full user permissions. They can modify, delete, or access any files your user account can access. Review and test all hook commands before adding them to your configuration.2054 Command hooks execute shell commands with your full user permissions. They can modify, delete, or access any files your user account can access. Review and test all hook commands before adding them to your configuration.
1372</Warning>2055</Warning>
1373 2056
1374### Security best practices2057### Security best practices
1385 2068
1386Run `claude --debug` to see hook execution details, including which hooks matched, their exit codes, and output. Toggle verbose mode with `Ctrl+O` to see hook progress in the transcript.2069Run `claude --debug` to see hook execution details, including which hooks matched, their exit codes, and output. Toggle verbose mode with `Ctrl+O` to see hook progress in the transcript.
1387 2070
1388```2071```text theme={null}
1389[DEBUG] Executing hooks for PostToolUse:Write2072[DEBUG] Executing hooks for PostToolUse:Write
1390[DEBUG] Getting matching hook commands for PostToolUse with query: Write2073[DEBUG] Getting matching hook commands for PostToolUse with query: Write
1391[DEBUG] Found 1 hook matchers in settings2074[DEBUG] Found 1 hook matchers in settings