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, 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: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. Events fall into three cadences: once per session (`SessionStart`, `SessionEnd`), once per turn (`UserPromptSubmit`, `Stop`, `StopFailure`), and on every tool call inside the agentic loop (`PreToolUse`, `PostToolUse`):
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/WLZtXlltXc8aIoIM/images/hooks-lifecycle.svg?fit=max&auto=format&n=WLZtXlltXc8aIoIM&q=85&s=6a0bf67eeb570a96e36b564721fa2a93" alt="Hook lifecycle diagram showing the sequence of hooks from SessionStart through the agentic loop (PreToolUse, PermissionRequest, PostToolUse, SubagentStart/Stop, TaskCreated, TaskCompleted) to Stop or StopFailure, TeammateIdle, PreCompact, PostCompact, and SessionEnd, with Elicitation and ElicitationResult nested inside MCP tool execution, PermissionDenied as a side branch from PermissionRequest for auto-mode denials, and WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, and FileChanged as standalone async events" width="520" height="1155" data-path="images/hooks-lifecycle.svg" />21 <img src="https://mintcdn.com/claude-code/ZIW26Z9pnpsXLhbS/images/hooks-lifecycle.svg?fit=max&auto=format&n=ZIW26Z9pnpsXLhbS&q=85&s=ee23691324deb6501df09bfdae560b64" alt="Hook lifecycle diagram showing optional Setup feeding into SessionStart, then a per-turn loop containing UserPromptSubmit, UserPromptExpansion for slash commands, the nested agentic loop (PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, PostToolBatch, SubagentStart/Stop, TaskCreated, TaskCompleted), and Stop or StopFailure, followed by TeammateIdle, PreCompact, PostCompact, and SessionEnd, with Elicitation and ElicitationResult nested inside MCP tool execution, PermissionDenied as a side branch from PermissionRequest for auto-mode denials, and WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, and FileChanged as standalone async events" width="520" height="1228" 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| `Setup` | When you start Claude Code with `--init-only`, or with `--init` or `--maintenance` in `-p` mode. For one-time preparation in CI or scripts |
30| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |31| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |
32| `UserPromptExpansion` | When a user-typed command expands into a prompt, before it reaches Claude. Can block the expansion |
31| `PreToolUse` | Before a tool call executes. Can block it |33| `PreToolUse` | Before a tool call executes. Can block it |
32| `PermissionRequest` | When a permission dialog appears |34| `PermissionRequest` | When a permission dialog appears |
33| `PermissionDenied` | When a tool call is denied by the auto mode classifier. Return `{retry: true}` to tell the model it may retry the denied tool call |35| `PermissionDenied` | When a tool call is denied by the auto mode classifier. Return `{retry: true}` to tell the model it may retry the denied tool call |
34| `PostToolUse` | After a tool call succeeds |36| `PostToolUse` | After a tool call succeeds |
35| `PostToolUseFailure` | After a tool call fails |37| `PostToolUseFailure` | After a tool call fails |
38| `PostToolBatch` | After a full batch of parallel tool calls resolves, before the next model call |
36| `Notification` | When Claude Code sends a notification |39| `Notification` | When Claude Code sends a notification |
37| `SubagentStart` | When a subagent is spawned |40| `SubagentStart` | When a subagent is spawned |
38| `SubagentStop` | When a subagent finishes |41| `SubagentStop` | When a subagent finishes |
55 58
56### How a hook resolves59### How a hook resolves
57 60
58To see how these pieces fit together, consider this `PreToolUse` hook that blocks destructive shell commands. The `matcher` narrows to Bash tool calls and the `if` condition narrows further to commands starting with `rm`, so `block-rm.sh` only spawns when both filters match:61To see how these pieces fit together, consider this `PreToolUse` hook that blocks destructive shell commands. The `matcher` narrows to Bash tool calls and the `if` condition narrows further to Bash subcommands matching `rm *`, so `block-rm.sh` only spawns when both filters match:
59 62
60```json theme={null}63```json theme={null}
61{64{
116 </Step>119 </Step>
117 120
118 <Step title="If condition checks">121 <Step title="If condition checks">
119 The `if` condition `"Bash(rm *)"` matches because the command starts with `rm`, so this handler spawns. If the command had been `npm test`, the `if` check would fail and `block-rm.sh` would never run, avoiding the process spawn overhead. The `if` field is optional; without it, every handler in the matched group runs.122 The `if` condition `"Bash(rm *)"` matches because `rm -rf /tmp/build` is a subcommand matching `rm *`, so this handler spawns. If the command had been `npm test`, the `if` check would fail and `block-rm.sh` would never run, avoiding the process spawn overhead. The `if` field is optional; without it, every handler in the matched group runs.
120 </Step>123 </Step>
121 124
122 <Step title="Hook handler runs">125 <Step title="Hook handler runs">
153See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.156See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.
154 157
155<Note>158<Note>
156 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.159 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, MCP tool, prompt, or agent that runs. "Hook" on its own refers to the general feature.
157</Note>160</Note>
158 161
159### Hook locations162### Hook locations
169| [Plugin](/en/plugins) `hooks/hooks.json` | When plugin is enabled | Yes, bundled with the plugin |172| [Plugin](/en/plugins) `hooks/hooks.json` | When plugin is enabled | Yes, bundled with the plugin |
170| [Skill](/en/skills) or [agent](/en/sub-agents) frontmatter | While the component is active | Yes, defined in the component file |173| [Skill](/en/skills) or [agent](/en/sub-agents) frontmatter | While the component is active | Yes, defined in the component file |
171 174
172For details on settings file resolution, see [settings](/en/settings). Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. See [Hook configuration](/en/settings#hook-configuration).175For details on settings file resolution, see [settings](/en/settings). Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. Hooks from plugins force-enabled in managed settings `enabledPlugins` are exempt, so administrators can distribute vetted hooks through an organization marketplace. See [Hook configuration](/en/settings#hook-configuration).
173 176
174### Matcher patterns177### Matcher patterns
175 178
186Each event type matches on a different field:189Each event type matches on a different field:
187 190
188| Event | What the matcher filters | Example matcher values |191| Event | What the matcher filters | Example matcher values |
189| :------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ |192| :------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
190| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |193| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |
191| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |194| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |
195| `Setup` | which CLI flag triggered setup | `init`, `maintenance` |
192| `SessionEnd` | why the session ended | `clear`, `resume`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |196| `SessionEnd` | why the session ended | `clear`, `resume`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |
193| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |197| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`, `elicitation_complete`, `elicitation_response` |
194| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |198| `SubagentStart` | agent type | `general-purpose`, `Explore`, `Plan`, or custom agent names |
195| `PreCompact`, `PostCompact` | what triggered compaction | `manual`, `auto` |199| `PreCompact`, `PostCompact` | what triggered compaction | `manual`, `auto` |
196| `SubagentStop` | agent type | same values as `SubagentStart` |200| `SubagentStop` | agent type | same values as `SubagentStart` |
197| `ConfigChange` | configuration source | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` |201| `ConfigChange` | configuration source | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` |
198| `CwdChanged` | no matcher support | always fires on every directory change |202| `CwdChanged` | no matcher support | always fires on every directory change |
199| `FileChanged` | literal filenames to watch (see [FileChanged](#filechanged)) | `.envrc\|.env` |203| `FileChanged` | literal filenames to watch (see [FileChanged](#filechanged)) | `.envrc\|.env` |
200| `StopFailure` | error type | `rate_limit`, `authentication_failed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, `unknown` |204| `StopFailure` | error type | `rate_limit`, `authentication_failed`, `oauth_org_not_allowed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, `unknown` |
201| `InstructionsLoaded` | load reason | `session_start`, `nested_traversal`, `path_glob_match`, `include`, `compact` |205| `InstructionsLoaded` | load reason | `session_start`, `nested_traversal`, `path_glob_match`, `include`, `compact` |
206| `UserPromptExpansion` | command name | your skill or command names |
202| `Elicitation` | MCP server name | your configured MCP server names |207| `Elicitation` | MCP server name | your configured MCP server names |
203| `ElicitationResult` | MCP server name | same values as `Elicitation` |208| `ElicitationResult` | MCP server name | same values as `Elicitation` |
204| `UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove` | no matcher support | always fires on every occurrence |209| `UserPromptSubmit`, `PostToolBatch`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove` | no matcher support | always fires on every occurrence |
205 210
206The 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.211The 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.
207 212
225}230}
226```231```
227 232
228`UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove`, and `CwdChanged` don't support matchers and always fire on every occurrence. If you add a `matcher` field to these events, it is silently ignored.233`UserPromptSubmit`, `PostToolBatch`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove`, and `CwdChanged` don't support matchers and always fire on every occurrence. If you add a `matcher` field to these events, it is silently ignored.
229 234
230For tool events, you can filter more narrowly by setting the [`if` field](#common-fields) on individual hook handlers. `if` uses [permission rule syntax](/en/permissions) to match against the tool name and arguments together, so `"Bash(git *)"` runs only for `git` commands and `"Edit(*.ts)"` runs only for TypeScript files.235For tool events, you can filter more narrowly by setting the [`if` field](#common-fields) on individual hook handlers. `if` uses [permission rule syntax](/en/permissions) to match against the tool name and arguments together, so `"Bash(git *)"` runs when any subcommand of the Bash input matches `git *` and `"Edit(*.ts)"` runs only for TypeScript files.
231 236
232#### Match MCP tools237#### Match MCP tools
233 238
275 280
276### Hook handler fields281### Hook handler fields
277 282
278Each 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:283Each object in the inner `hooks` array is a hook handler: the shell command, HTTP endpoint, MCP tool, LLM prompt, or agent that runs when the matcher matches. There are five types:
279 284
280* **[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.285* **[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.
281* **[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.286* **[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.
287* **[MCP tool hooks](#mcp-tool-hook-fields)** (`type: "mcp_tool"`): call a tool on an already-connected [MCP server](/en/mcp). The tool's text output is treated like command-hook stdout.
282* **[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).288* **[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).
283* **[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).289* **[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. Agent hooks are experimental and may change. See [Agent-based hooks](#agent-based-hooks).
284 290
285#### Common fields291#### Common fields
286 292
287These fields apply to all hook types:293These fields apply to all hook types:
288 294
289| Field | Required | Description |295| Field | Required | Description |
290| :-------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |296| :-------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
291| `type` | yes | `"command"`, `"http"`, `"prompt"`, or `"agent"` |297| `type` | yes | `"command"`, `"http"`, `"mcp_tool"`, `"prompt"`, or `"agent"` |
292| `if` | no | Permission rule syntax to filter when this hook runs, such as `"Bash(git *)"` or `"Edit(*.ts)"`. The hook only spawns if the tool call matches the pattern. Only evaluated on tool events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, and `PermissionDenied`. On other events, a hook with `if` set never runs. Uses the same syntax as [permission rules](/en/permissions) |298| `if` | no | Permission rule syntax to filter when this hook runs, such as `"Bash(git *)"` or `"Edit(*.ts)"`. The hook only spawns if the tool call matches the pattern, or if a Bash command is too complex to parse. Only evaluated on tool events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, and `PermissionDenied`. On other events, a hook with `if` set never runs. Uses the same syntax as [permission rules](/en/permissions) |
293| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |299| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |
294| `statusMessage` | no | Custom spinner message displayed while the hook runs |300| `statusMessage` | no | Custom spinner message displayed while the hook runs |
295| `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) |301| `once` | no | If `true`, runs once per session then is removed. Only honored for hooks declared in [skill frontmatter](#hooks-in-skills-and-agents); ignored in settings files and agent frontmatter |
302
303The `if` field holds exactly one permission rule. There is no `&&`, `||`, or list syntax for combining rules; to apply multiple conditions, define a separate hook handler for each. For Bash, the rule is matched against each subcommand of the tool input after leading `VAR=value` assignments are stripped, so `if: "Bash(git push *)"` matches both `FOO=bar git push` and `npm test && git push`. The hook runs if any subcommand matches, and always runs when the command is too complex to parse.
296 304
297#### Command hook fields305#### Command hook fields
298 306
299In addition to the [common fields](#common-fields), command hooks accept these fields:307In addition to the [common fields](#common-fields), command hooks accept these fields:
300 308
301| Field | Required | Description |309| Field | Required | Description |
302| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |310| :------------ | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
303| `command` | yes | Shell command to execute |311| `command` | yes | Shell command to execute |
304| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |312| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |
313| `asyncRewake` | no | If `true`, runs in the background and wakes Claude on exit code 2. Implies `async`. The hook's stderr, or stdout if stderr is empty, is shown to Claude as a system reminder so it can react to a long-running background failure |
305| `shell` | no | Shell to use for this hook. Accepts `"bash"` (default) or `"powershell"`. Setting `"powershell"` runs the command via PowerShell on Windows. Does not require `CLAUDE_CODE_USE_POWERSHELL_TOOL` since hooks spawn PowerShell directly |314| `shell` | no | Shell to use for this hook. Accepts `"bash"` (default) or `"powershell"`. Setting `"powershell"` runs the command via PowerShell on Windows. Does not require `CLAUDE_CODE_USE_POWERSHELL_TOOL` since hooks spawn PowerShell directly |
306 315
307#### HTTP hook fields316#### HTTP hook fields
343}352}
344```353```
345 354
355#### MCP tool hook fields
356
357In addition to the [common fields](#common-fields), MCP tool hooks accept these fields:
358
359| Field | Required | Description |
360| :------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
361| `server` | yes | Name of a configured MCP server. The server must already be connected; the hook never triggers an OAuth or connection flow |
362| `tool` | yes | Name of the tool to call on that server |
363| `input` | no | Arguments passed to the tool. String values support `${path}` substitution from the hook's [JSON input](#hook-input-and-output), such as `"${tool_input.file_path}"` |
364
365The tool's text content is treated like command-hook stdout: if it parses as valid [JSON output](#json-output) it is processed as a decision, otherwise it is shown as plain text. If the named server is not connected, or the tool returns `isError: true`, the hook produces a non-blocking error and execution continues.
366
367MCP tool hooks are available on every hook event once Claude Code has connected to your MCP servers. `SessionStart` and `Setup` typically fire before servers finish connecting, so hooks on those events should expect the "not connected" error on first run.
368
369This example calls the `security_scan` tool on the `my_server` MCP server after each `Write` or `Edit`, passing the edited file's path:
370
371```json theme={null}
372{
373 "hooks": {
374 "PostToolUse": [
375 {
376 "matcher": "Write|Edit",
377 "hooks": [
378 {
379 "type": "mcp_tool",
380 "server": "my_server",
381 "tool": "security_scan",
382 "input": { "file_path": "${tool_input.file_path}" }
383 }
384 ]
385 }
386 ]
387 }
388}
389```
390
346#### Prompt and agent hook fields391#### Prompt and agent hook fields
347 392
348In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:393In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:
443 488
444Type `/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.489Type `/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.
445 490
446The 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:491The menu displays all five hook types: `command`, `prompt`, `agent`, `http`, and `mcp_tool`. Each hook is labeled with a `[type]` prefix and a source indicating where it was defined:
447 492
448* `User`: from `~/.claude/settings.json`493* `User`: from `~/.claude/settings.json`
449* `Project`: from `.claude/settings.json`494* `Project`: from `.claude/settings.json`
509 554
510The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.555The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.
511 556
512**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 written to the debug log but not shown in the transcript. The exceptions are `UserPromptSubmit` and `SessionStart`, where stdout is added as context that Claude can see and act on.557**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 written to the debug log but not shown in the transcript. The exceptions are `UserPromptSubmit`, `UserPromptExpansion`, and `SessionStart`, where stdout is added as context that Claude can see and act on.
513 558
514**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.559**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.
515 560
516**Any other exit code** is a non-blocking error for most hook events. The transcript shows a one-line `<hook name> hook error` notice and execution continues. The full stderr is written to the debug log.561**Any other exit code** is a non-blocking error for most hook events. The transcript shows a `<hook name> hook error` notice followed by the first line of stderr, so you can identify the cause without `--debug`. Execution continues and the full stderr is written to the debug log.
517 562
518For example, a hook command script that blocks dangerous Bash commands:563For example, a hook command script that blocks dangerous Bash commands:
519 564
539Exit 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.584Exit 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.
540 585
541| Hook event | Can block? | What happens on exit 2 |586| Hook event | Can block? | What happens on exit 2 |
542| :------------------- | :--------- | :----------------------------------------------------------------------------------------------------------------------------------- |587| :-------------------- | :--------- | :----------------------------------------------------------------------------------------------------------------------------------- |
543| `PreToolUse` | Yes | Blocks the tool call |588| `PreToolUse` | Yes | Blocks the tool call |
544| `PermissionRequest` | Yes | Denies the permission |589| `PermissionRequest` | Yes | Denies the permission |
545| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |590| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |
591| `UserPromptExpansion` | Yes | Blocks the expansion |
546| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |592| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |
547| `SubagentStop` | Yes | Prevents the subagent from stopping |593| `SubagentStop` | Yes | Prevents the subagent from stopping |
548| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |594| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |
552| `StopFailure` | No | Output and exit code are ignored |598| `StopFailure` | No | Output and exit code are ignored |
553| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |599| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |
554| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |600| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |
601| `PostToolBatch` | Yes | Stops the agentic loop before the next model call |
555| `PermissionDenied` | No | Exit code and stderr are ignored (denial already occurred). Use JSON `hookSpecificOutput.retry: true` to tell the model it may retry |602| `PermissionDenied` | No | Exit code and stderr are ignored (denial already occurred). Use JSON `hookSpecificOutput.retry: true` to tell the model it may retry |
556| `Notification` | No | Shows stderr to user only |603| `Notification` | No | Shows stderr to user only |
557| `SubagentStart` | No | Shows stderr to user only |604| `SubagentStart` | No | Shows stderr to user only |
558| `SessionStart` | No | Shows stderr to user only |605| `SessionStart` | No | Shows stderr to user only |
606| `Setup` | No | Shows stderr to user only |
559| `SessionEnd` | No | Shows stderr to user only |607| `SessionEnd` | No | Shows stderr to user only |
560| `CwdChanged` | No | Shows stderr to user only |608| `CwdChanged` | No | Shows stderr to user only |
561| `FileChanged` | No | Shows stderr to user only |609| `FileChanged` | No | Shows stderr to user only |
562| `PreCompact` | No | Shows stderr to user only |610| `PreCompact` | Yes | Blocks compaction |
563| `PostCompact` | No | Shows stderr to user only |611| `PostCompact` | No | Shows stderr to user only |
564| `Elicitation` | Yes | Denies the elicitation |612| `Elicitation` | Yes | Denies the elicitation |
565| `ElicitationResult` | Yes | Blocks the response (action becomes decline) |613| `ElicitationResult` | Yes | Blocks the response (action becomes decline) |
610{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }658{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
611```659```
612 660
661#### Add context for Claude
662
663The `additionalContext` field passes a string from your hook into Claude's context window. Claude Code wraps the string in a system reminder and inserts it into the conversation at the point where the hook fired. Claude reads the reminder on the next model request, but it does not appear as a chat message in the interface.
664
665Return `additionalContext` inside `hookSpecificOutput` alongside the event name:
666
667```json theme={null}
668{
669 "hookSpecificOutput": {
670 "hookEventName": "PostToolUse",
671 "additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
672 }
673}
674```
675
676Where the reminder appears depends on the event:
677
678* [SessionStart](#sessionstart), [Setup](#setup), and [SubagentStart](#subagentstart): at the start of the conversation, before the first prompt
679* [UserPromptSubmit](#userpromptsubmit) and [UserPromptExpansion](#userpromptexpansion): alongside the submitted prompt
680* [PreToolUse](#pretooluse), [PostToolUse](#posttooluse), [PostToolUseFailure](#posttoolusefailure), and [PostToolBatch](#posttoolbatch): next to the tool result
681
682When several hooks return `additionalContext` for the same event, Claude receives all of the values. If a value exceeds 10,000 characters, Claude Code writes the full text to a file in the session directory and passes Claude the file path with a short preview instead.
683
684Use `additionalContext` for information Claude should know about the current state of your environment or the operation that just ran:
685
686* **Environment state**: the current branch, deployment target, or active feature flags
687* **Conditional project rules**: which test command applies to the file just edited, which directories are read-only in this worktree
688* **External data**: open issues assigned to you, recent CI results, content fetched from an internal service
689
690For instructions that never change, prefer [CLAUDE.md](/en/memory). It loads without running a script and is the standard place for static project conventions.
691
692Write the text as factual statements rather than imperative system instructions. Phrasing such as "The deployment target is production" or "This repo uses `bun test`" reads as project information. Text framed as out-of-band system commands can trigger Claude's prompt-injection defenses, which causes Claude to surface the text to you instead of treating it as context.
693
694Once injected, the text is saved in the session transcript. For mid-session events like `PostToolUse` or `UserPromptSubmit`, resuming with `--continue` or `--resume` replays the saved text rather than re-running the hook for past turns, so values like timestamps or commit SHAs become stale on resume. `SessionStart` hooks run again on resume with `source` set to `"resume"`, so they can refresh their context.
695
613#### Decision control696#### Decision control
614 697
615Not 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:698Not 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:
616 699
617| Events | Decision pattern | Key fields |700| Events | Decision pattern | Key fields |
618| :-------------------------------------------------------------------------------------------------------------------------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |701| :---------------------------------------------------------------------------------------------------------------------------------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
619| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange | Top-level `decision` | `decision: "block"`, `reason` |702| UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact | Top-level `decision` | `decision: "block"`, `reason` |
620| TeammateIdle, TaskCreated, 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 |703| TeammateIdle, TaskCreated, 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 |
621| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask/defer), `permissionDecisionReason` |704| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask/defer), `permissionDecisionReason` |
622| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |705| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |
624| WorktreeCreate | path return | Command hook prints path on stdout; HTTP hook returns `hookSpecificOutput.worktreePath`. Hook failure or missing path fails creation |707| WorktreeCreate | path return | Command hook prints path on stdout; HTTP hook returns `hookSpecificOutput.worktreePath`. Hook failure or missing path fails creation |
625| Elicitation | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values for accept) |708| Elicitation | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values for accept) |
626| ElicitationResult | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values override) |709| ElicitationResult | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values override) |
627| WorktreeRemove, Notification, SessionEnd, PreCompact, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, FileChanged | None | No decision control. Used for side effects like logging or cleanup |710| WorktreeRemove, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, FileChanged | None | No decision control. Used for side effects like logging or cleanup |
628 711
629Here are examples of each pattern in action:712Here are examples of each pattern in action:
630 713
631<Tabs>714<Tabs>
632 <Tab title="Top-level decision">715 <Tab title="Top-level decision">
633 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:716 Used by `UserPromptSubmit`, `UserPromptExpansion`, `PostToolUse`, `PostToolUseFailure`, `PostToolBatch`, `Stop`, `SubagentStop`, `ConfigChange`, and `PreCompact`. The only value is `"block"`. To allow the action to proceed, omit `decision` from your JSON, or exit 0 without any JSON at all:
634 717
635 ```json theme={null}718 ```json theme={null}
636 {719 {
683 766
684Runs 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.767Runs 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.
685 768
686SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` hooks are supported.769SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` and `type: "mcp_tool"` hooks are supported.
687 770
688The matcher value corresponds to how the session was initiated:771The matcher value corresponds to how the session was initiated:
689 772
714Any text your hook script prints to stdout is added as context for Claude. In addition to the [JSON output fields](#json-output) available to all hooks, you can return these event-specific fields:797Any text your hook script prints to stdout is added as context for Claude. In addition to the [JSON output fields](#json-output) available to all hooks, you can return these event-specific fields:
715 798
716| Field | Description |799| Field | Description |
717| :------------------ | :------------------------------------------------------------------------ |800| :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
718| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |801| `additionalContext` | String added to Claude's context at the start of the conversation, before the first prompt. See [Add context for Claude](#add-context-for-claude) for how the text is delivered and what to put in it |
719 802
720```json theme={null}803```json theme={null}
721{804{
722 "hookSpecificOutput": {805 "hookSpecificOutput": {
723 "hookEventName": "SessionStart",806 "hookEventName": "SessionStart",
724 "additionalContext": "My additional context here"807 "additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2"
725 }808 }
726}809}
727```810```
728 811
812Since plain stdout already reaches Claude for this event, a hook that only loads context can print to stdout directly without building JSON. Use the JSON form when you need to combine context with other fields such as `suppressOutput`.
813
729#### Persist environment variables814#### Persist environment variables
730 815
731SessionStart hooks have access to the `CLAUDE_ENV_FILE` environment variable, which provides a file path where you can persist environment variables for subsequent Bash commands.816SessionStart hooks have access to the `CLAUDE_ENV_FILE` environment variable, which provides a file path where you can persist environment variables for subsequent Bash commands.
766Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.851Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.
767 852
768<Note>853<Note>
769 `CLAUDE_ENV_FILE` is available for SessionStart, [CwdChanged](#cwdchanged), and [FileChanged](#filechanged) hooks. Other hook types do not have access to this variable.854 `CLAUDE_ENV_FILE` is available for SessionStart, [Setup](#setup), [CwdChanged](#cwdchanged), and [FileChanged](#filechanged) hooks. Other hook types do not have access to this variable.
770</Note>855</Note>
771 856
857### Setup
858
859Fires only when you launch Claude Code with `--init-only`, or with `--init` or `--maintenance` in print mode (`-p`). It does not fire on normal startup. Use it for one-time dependency installation or scheduled cleanup that you trigger explicitly from CI or scripts, separate from normal session startup. For per-session initialization, use [SessionStart](#sessionstart) instead.
860
861The matcher value corresponds to the CLI flag that triggered the hook:
862
863| Matcher | When it fires |
864| :------------ | :----------------------------------------- |
865| `init` | `claude --init-only` or `claude -p --init` |
866| `maintenance` | `claude -p --maintenance` |
867
868`--init-only` runs Setup hooks and `SessionStart` hooks with the `startup` matcher, then exits without starting a conversation. `--init` and `--maintenance` fire Setup hooks only when combined with `-p` (print mode); in an interactive session those two flags do not currently fire Setup hooks.
869
870Because Setup does not fire on every launch, a plugin that needs a dependency installed cannot rely on Setup alone. The practical pattern is to check for the dependency on first use and install on miss, for example a hook or skill that tests for `${CLAUDE_PLUGIN_DATA}/node_modules` and runs `npm install` if absent. See the [persistent data directory](/en/plugins-reference#persistent-data-directory) for where to store installed dependencies.
871
872#### Setup input
873
874In addition to the [common input fields](#common-input-fields), Setup hooks receive a `trigger` field set to either `"init"` or `"maintenance"`:
875
876```json theme={null}
877{
878 "session_id": "abc123",
879 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
880 "cwd": "/Users/...",
881 "hook_event_name": "Setup",
882 "trigger": "init"
883}
884```
885
886#### Setup decision control
887
888Setup hooks cannot block. On exit code 2, stderr is shown to the user; on any other non-zero exit code, stderr appears only when you launch with `--verbose`. In both cases execution continues. To pass information into Claude's context, return `additionalContext` in JSON output; plain stdout is written to the debug log only. In addition to the [JSON output fields](#json-output) available to all hooks, you can return these event-specific fields:
889
890| Field | Description |
891| :------------------ | :------------------------------------------------------------------------ |
892| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |
893
894```json theme={null}
895{
896 "hookSpecificOutput": {
897 "hookEventName": "Setup",
898 "additionalContext": "Dependencies installed: node_modules, .venv"
899 }
900}
901```
902
903Setup hooks have access to `CLAUDE_ENV_FILE`. Variables written to that file persist into subsequent Bash commands for the session, just as in [SessionStart hooks](#persist-environment-variables). Only `type: "command"` and `type: "mcp_tool"` hooks are supported.
904
772### InstructionsLoaded905### InstructionsLoaded
773 906
774Fires 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.907Fires 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.
839To block a prompt, return a JSON object with `decision` set to `"block"`:972To block a prompt, return a JSON object with `decision` set to `"block"`:
840 973
841| Field | Description |974| Field | Description |
842| :------------------ | :----------------------------------------------------------------------------------------------------------------- |975| :------------------ | :--------------------------------------------------------------------------------------------------------------------- |
843| `decision` | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |976| `decision` | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |
844| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |977| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |
845| `additionalContext` | String added to Claude's context |978| `additionalContext` | String added to Claude's context alongside the submitted prompt. See [Add context for Claude](#add-context-for-claude) |
846| `sessionTitle` | Sets the session title, same effect as `/rename`. Use to name sessions automatically based on the prompt content |979| `sessionTitle` | Sets the session title, same effect as `/rename`. Use to name sessions automatically based on the prompt content |
847 980
848```json theme={null}981```json theme={null}
862 block prompts or want more structured control.995 block prompts or want more structured control.
863</Note>996</Note>
864 997
998### UserPromptExpansion
999
1000Runs when a user-typed slash command expands into a prompt before reaching Claude. Use this to block specific commands from direct invocation, inject context for a particular skill, or log which commands users invoke. For example, a hook matching `deploy` can block `/deploy` unless an approval file is present, or a hook matching a review skill can append the team's review checklist as `additionalContext`.
1001
1002This event covers the path `PreToolUse` does not: a `PreToolUse` hook matching the `Skill` tool fires only when Claude calls the tool, but typing `/skillname` directly bypasses `PreToolUse`. `UserPromptExpansion` fires on that direct path.
1003
1004Matches on `command_name`. Leave the matcher empty to fire on every prompt-type slash command.
1005
1006#### UserPromptExpansion input
1007
1008In addition to the [common input fields](#common-input-fields), UserPromptExpansion hooks receive `expansion_type`, `command_name`, `command_args`, `command_source`, and the original `prompt` string. The `expansion_type` field is `slash_command` for skill and custom commands, or `mcp_prompt` for MCP server prompts.
1009
1010```json theme={null}
1011{
1012 "session_id": "abc123",
1013 "transcript_path": "/Users/.../00893aaf.jsonl",
1014 "cwd": "/Users/...",
1015 "permission_mode": "default",
1016 "hook_event_name": "UserPromptExpansion",
1017 "expansion_type": "slash_command",
1018 "command_name": "example-skill",
1019 "command_args": "arg1 arg2",
1020 "command_source": "plugin",
1021 "prompt": "/example-skill arg1 arg2"
1022}
1023```
1024
1025#### UserPromptExpansion decision control
1026
1027`UserPromptExpansion` hooks can block the expansion or add context. All [JSON output fields](#json-output) are available.
1028
1029| Field | Description |
1030| :------------------ | :-------------------------------------------------------------------------------------------------------------------- |
1031| `decision` | `"block"` prevents the slash command from expanding. Omit to allow it to proceed |
1032| `reason` | Shown to the user when `decision` is `"block"` |
1033| `additionalContext` | String added to Claude's context alongside the expanded prompt. See [Add context for Claude](#add-context-for-claude) |
1034
1035```json theme={null}
1036{
1037 "decision": "block",
1038 "reason": "This slash command is not available",
1039 "hookSpecificOutput": {
1040 "hookEventName": "UserPromptExpansion",
1041 "additionalContext": "Additional context for this expansion"
1042 }
1043}
1044```
1045
865### PreToolUse1046### PreToolUse
866 1047
867Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name: `Bash`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Agent`, `WebFetch`, `WebSearch`, `AskUserQuestion`, `ExitPlanMode`, and any [MCP tool names](#match-mcp-tools).1048Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name: `Bash`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Agent`, `WebFetch`, `WebSearch`, `AskUserQuestion`, `ExitPlanMode`, and any [MCP tool names](#match-mcp-tools).
979`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: four outcomes (allow, deny, ask, or defer) plus the ability to modify tool input before execution.1160`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: four outcomes (allow, deny, ask, or defer) plus the ability to modify tool input before execution.
980 1161
981| Field | Description |1162| Field | Description |
982| :------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |1163| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
983| `permissionDecision` | `"allow"` skips the permission prompt. `"deny"` prevents the tool call. `"ask"` prompts the user to confirm. `"defer"` exits gracefully so the tool can be resumed later. [Deny and ask rules](/en/permissions#manage-permissions) still apply when a hook returns `"allow"` |1164| `permissionDecision` | `"allow"` skips the permission prompt. `"deny"` prevents the tool call. `"ask"` prompts the user to confirm. `"defer"` exits gracefully so the tool can be resumed later. [Deny and ask rules](/en/permissions#manage-permissions) are still evaluated regardless of what the hook returns |
984| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude. For `"defer"`, ignored |1165| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude. For `"defer"`, ignored |
985| `updatedInput` | Modifies the tool's input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones. Combine with `"allow"` to auto-approve, or `"ask"` to show the modified input to the user. For `"defer"`, ignored |1166| `updatedInput` | Modifies the tool's input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones. Combine with `"allow"` to auto-approve, or `"ask"` to show the modified input to the user. For `"defer"`, ignored |
986| `additionalContext` | String added to Claude's context before the tool executes. For `"defer"`, ignored |1167| `additionalContext` | String added to Claude's context alongside the tool result. Ignored when `permissionDecision` is `"defer"`. See [Add context for Claude](#add-context-for-claude) |
987 1168
988When multiple PreToolUse hooks return different decisions, precedence is `deny` > `defer` > `ask` > `allow`.1169When multiple PreToolUse hooks return different decisions, precedence is `deny` > `defer` > `ask` > `allow`.
989 1170
1041}1222}
1042```1223```
1043 1224
1044There is no timeout or retry limit. The session remains on disk until you resume it. If the answer is not ready when you resume, the hook can return `"defer"` again and the process exits the same way. The calling process controls when to break the loop by eventually returning `"allow"` or `"deny"` from the hook.1225There is no timeout or retry limit. The session remains on disk until you resume it, subject to the [`cleanupPeriodDays`](/en/settings#available-settings) retention sweep that deletes session files after 30 days by default. If the answer is not ready when you resume, the hook can return `"defer"` again and the process exits the same way. The calling process controls when to break the loop by eventually returning `"allow"` or `"deny"` from the hook.
1045 1226
1046`"defer"` only works when Claude makes a single tool call in the turn. If Claude makes several tool calls at once, `"defer"` is ignored with a warning and the tool proceeds through the normal permission flow. The constraint exists because resume can only re-run one tool: there is no way to defer one call from a batch without leaving the others unresolved.1227`"defer"` only works when Claude makes a single tool call in the turn. If Claude makes several tool calls at once, `"defer"` is ignored with a warning and the tool proceeds through the normal permission flow. The constraint exists because resume can only re-run one tool: there is no way to defer one call from a batch without leaving the others unresolved.
1047 1228
1090`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:1271`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:
1091 1272
1092| Field | Description |1273| Field | Description |
1093| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |1274| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1094| `behavior` | `"allow"` grants the permission, `"deny"` denies it |1275| `behavior` | `"allow"` grants the permission, `"deny"` denies it. [Deny and ask rules](/en/permissions#manage-permissions) are still evaluated, so a hook returning `"allow"` does not override a matching deny rule |
1095| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones |1276| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones. The modified input is re-evaluated against deny and ask rules |
1096| `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 |1277| `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 |
1097| `message` | For `"deny"` only: tells Claude why the permission was denied |1278| `message` | For `"deny"` only: tells Claude why the permission was denied |
1098| `interrupt` | For `"deny"` only: if `true`, stops Claude |1279| `interrupt` | For `"deny"` only: if `true`, stops Claude |
1124| `addDirectories` | `directories`, `destination` | Adds working directories. `directories` is an array of path strings |1305| `addDirectories` | `directories`, `destination` | Adds working directories. `directories` is an array of path strings |
1125| `removeDirectories` | `directories`, `destination` | Removes working directories |1306| `removeDirectories` | `directories`, `destination` | Removes working directories |
1126 1307
1308<Note>
1309 `setMode` with `bypassPermissions` only takes effect if the session was launched with bypass mode already available: `--dangerously-skip-permissions`, `--permission-mode bypassPermissions`, `--allow-dangerously-skip-permissions`, or `permissions.defaultMode: "bypassPermissions"` in settings, and the mode is not disabled by [`permissions.disableBypassPermissionsMode`](/en/permissions#managed-settings). Otherwise the update is a no-op. `bypassPermissions` is never persisted as `defaultMode` regardless of `destination`.
1310</Note>
1311
1127The `destination` field on every entry determines whether the change stays in memory or persists to a settings file.1312The `destination` field on every entry determines whether the change stays in memory or persists to a settings file.
1128 1313
1129| `destination` | Writes to |1314| `destination` | Writes to |
1161 "filePath": "/path/to/file.txt",1346 "filePath": "/path/to/file.txt",
1162 "success": true1347 "success": true
1163 },1348 },
1164 "tool_use_id": "toolu_01ABC123..."1349 "tool_use_id": "toolu_01ABC123...",
1350 "duration_ms": 12
1165}1351}
1166```1352```
1167 1353
1354| Field | Description |
1355| :------------ | :------------------------------------------------------------------------------------------------------------ |
1356| `duration_ms` | Optional. Tool execution time in milliseconds. Excludes time spent in permission prompts and PreToolUse hooks |
1357
1168#### PostToolUse decision control1358#### PostToolUse decision control
1169 1359
1170`PostToolUse` hooks can provide feedback to Claude after tool execution. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:1360`PostToolUse` hooks can provide feedback to Claude after tool execution. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:
1171 1361
1172| Field | Description |1362| Field | Description |
1173| :--------------------- | :----------------------------------------------------------------------------------------- |1363| :--------------------- | :--------------------------------------------------------------------------------------------------------------------------- |
1174| `decision` | `"block"` prompts Claude with the `reason`. Omit to allow the action to proceed |1364| `decision` | `"block"` prompts Claude with the `reason`. Omit to allow the action to proceed |
1175| `reason` | Explanation shown to Claude when `decision` is `"block"` |1365| `reason` | Explanation shown to Claude when `decision` is `"block"` |
1176| `additionalContext` | Additional context for Claude to consider |1366| `additionalContext` | String added to Claude's context alongside the tool result. See [Add context for Claude](#add-context-for-claude) |
1177| `updatedMCPToolOutput` | For [MCP tools](#match-mcp-tools) only: replaces the tool's output with the provided value |1367| `updatedToolOutput` | Replaces the tool's output with the provided value before it is sent to Claude. The value must match the tool's output shape |
1368| `updatedMCPToolOutput` | Replaces the output for [MCP tools](#match-mcp-tools) only. Prefer `updatedToolOutput`, which works for all tools |
1369
1370The example below replaces the output of a `Bash` call. The replacement value matches the `Bash` tool's output shape:
1178 1371
1179```json theme={null}1372```json theme={null}
1180{1373{
1181 "decision": "block",
1182 "reason": "Explanation for decision",
1183 "hookSpecificOutput": {1374 "hookSpecificOutput": {
1184 "hookEventName": "PostToolUse",1375 "hookEventName": "PostToolUse",
1185 "additionalContext": "Additional information for Claude"1376 "additionalContext": "Additional information for Claude",
1377 "updatedToolOutput": {
1378 "stdout": "[redacted]",
1379 "stderr": "",
1380 "interrupted": false,
1381 "isImage": false
1382 }
1186 }1383 }
1187}1384}
1188```1385```
1189 1386
1387<Warning>
1388 `updatedToolOutput` only changes what Claude sees. The tool has already run by the time the hook fires, so any files written, commands executed, or network requests sent have already taken effect. Telemetry such as OpenTelemetry tool spans and analytics events also captures the original output before the hook runs. To prevent or modify a tool call before it runs, use a [PreToolUse](#pretooluse) hook instead.
1389
1390 The replacement value must match the tool's output shape. Built-in tools return structured objects rather than plain strings. For example, `Bash` returns an object with `stdout`, `stderr`, `interrupted`, and `isImage` fields. For built-in tools, a value that does not match the tool's output schema is ignored and the original output is used. MCP tool output is passed through without schema validation. Stripping error details that Claude needs can cause it to proceed on a false assumption.
1391</Warning>
1392
1190### PostToolUseFailure1393### PostToolUseFailure
1191 1394
1192Runs when a tool execution fails. This event fires for tool calls that throw errors or return failure results. Use this to log failures, send alerts, or provide corrective feedback to Claude.1395Runs when a tool execution fails. This event fires for tool calls that throw errors or return failure results. Use this to log failures, send alerts, or provide corrective feedback to Claude.
1211 },1414 },
1212 "tool_use_id": "toolu_01ABC123...",1415 "tool_use_id": "toolu_01ABC123...",
1213 "error": "Command exited with non-zero status code 1",1416 "error": "Command exited with non-zero status code 1",
1214 "is_interrupt": false1417 "is_interrupt": false,
1418 "duration_ms": 4187
1215}1419}
1216```1420```
1217 1421
1218| Field | Description |1422| Field | Description |
1219| :------------- | :------------------------------------------------------------------------------ |1423| :------------- | :------------------------------------------------------------------------------------------------------------ |
1220| `error` | String describing what went wrong |1424| `error` | String describing what went wrong |
1221| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |1425| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |
1426| `duration_ms` | Optional. Tool execution time in milliseconds. Excludes time spent in permission prompts and PreToolUse hooks |
1222 1427
1223#### PostToolUseFailure decision control1428#### PostToolUseFailure decision control
1224 1429
1225`PostToolUseFailure` hooks can provide context to Claude after a tool failure. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:1430`PostToolUseFailure` hooks can provide context to Claude after a tool failure. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:
1226 1431
1227| Field | Description |1432| Field | Description |
1228| :------------------ | :------------------------------------------------------------ |1433| :------------------ | :---------------------------------------------------------------------------------------------------------- |
1229| `additionalContext` | Additional context for Claude to consider alongside the error |1434| `additionalContext` | String added to Claude's context alongside the error. See [Add context for Claude](#add-context-for-claude) |
1230 1435
1231```json theme={null}1436```json theme={null}
1232{1437{
1237}1442}
1238```1443```
1239 1444
1445### PostToolBatch
1446
1447Runs once after every tool call in a batch has resolved, before Claude Code sends the next request to the model. `PostToolUse` fires once per tool, which means it fires concurrently when Claude makes parallel tool calls. `PostToolBatch` fires exactly once with the full batch, so it is the right place to inject context that depends on the set of tools that ran rather than on any single tool. There is no matcher for this event.
1448
1449#### PostToolBatch input
1450
1451In addition to the [common input fields](#common-input-fields), PostToolBatch hooks receive `tool_calls`, an array describing every tool call in the batch:
1452
1453```json theme={null}
1454{
1455 "session_id": "abc123",
1456 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1457 "cwd": "/Users/...",
1458 "permission_mode": "default",
1459 "hook_event_name": "PostToolBatch",
1460 "tool_calls": [
1461 {
1462 "tool_name": "Read",
1463 "tool_input": {"file_path": "/.../ledger/accounts.py"},
1464 "tool_use_id": "toolu_01...",
1465 "tool_response": " 1\tfrom __future__ import annotations\n 2\t..."
1466 },
1467 {
1468 "tool_name": "Read",
1469 "tool_input": {"file_path": "/.../ledger/transactions.py"},
1470 "tool_use_id": "toolu_02...",
1471 "tool_response": " 1\tfrom __future__ import annotations\n 2\t..."
1472 }
1473 ]
1474}
1475```
1476
1477`tool_response` contains the same content the model receives in the corresponding `tool_result` block. The value is a serialized string or content-block array, exactly as the tool emitted it. For `Read`, that means line-number-prefixed text rather than raw file contents. Responses can be large, so parse only the fields you need.
1478
1479<Note>
1480 The `tool_response` shape differs from `PostToolUse`'s. `PostToolUse` passes the tool's structured `Output` object, such as `{filePath: "...", success: true}` for `Write`; `PostToolBatch` passes the serialized `tool_result` content the model sees.
1481</Note>
1482
1483#### PostToolBatch decision control
1484
1485`PostToolBatch` hooks can inject context for Claude. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:
1486
1487| Field | Description |
1488| :------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1489| `additionalContext` | Context string injected once before the next model call. See [Add context for Claude](#add-context-for-claude) for delivery details, what to put in it, and how resumed sessions handle past values |
1490
1491```json theme={null}
1492{
1493 "hookSpecificOutput": {
1494 "hookEventName": "PostToolBatch",
1495 "additionalContext": "These files are part of the ledger module. Run pytest before marking the task complete."
1496 }
1497}
1498```
1499
1500Returning `decision: "block"` or `continue: false` stops the agentic loop before the next model call.
1501
1240### PermissionDenied1502### PermissionDenied
1241 1503
1242Runs when the [auto mode](/en/permission-modes#eliminate-prompts-with-auto-mode) classifier denies a tool call. This hook only fires in auto mode: it does not run when you manually deny a permission dialog, when a `PreToolUse` hook blocks a call, or when a `deny` rule matches. Use it to log classifier denials, adjust configuration, or tell the model it may retry the tool call.1504Runs when the [auto mode](/en/permission-modes#eliminate-prompts-with-auto-mode) classifier denies a tool call. This hook only fires in auto mode: it does not run when you manually deny a permission dialog, when a `PreToolUse` hook blocks a call, or when a `deny` rule matches. Use it to log classifier denials, adjust configuration, or tell the model it may retry the tool call.
1285 1547
1286### Notification1548### Notification
1287 1549
1288Runs when Claude Code sends notifications. Matches on notification type: `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`. Omit the matcher to run hooks for all notification types.1550Runs when Claude Code sends notifications. Matches on notification type: `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`, `elicitation_complete`, `elicitation_response`. Omit the matcher to run hooks for all notification types.
1289 1551
1290Use separate matchers to run different handlers depending on the notification type. This configuration triggers a permission-specific alert script when Claude needs permission approval and a different notification when Claude has been idle:1552Use separate matchers to run different handlers depending on the notification type. This configuration triggers a permission-specific alert script when Claude needs permission approval and a different notification when Claude has been idle:
1291 1553
1332}1594}
1333```1595```
1334 1596
1335Notification hooks cannot block or modify notifications. In addition to the [JSON output fields](#json-output) available to all hooks, you can return `additionalContext` to add context to the conversation:1597Notification hooks cannot block or modify notifications. They are intended for side effects such as forwarding the notification to an external service. The [common JSON output fields](#json-output) such as `systemMessage` apply.
1336
1337| Field | Description |
1338| :------------------ | :------------------------------- |
1339| `additionalContext` | String added to Claude's context |
1340 1598
1341### SubagentStart1599### SubagentStart
1342 1600
1343Runs 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/`).1601Runs when a Claude Code subagent is spawned via the Agent tool. Supports matchers to filter by agent type name (built-in agents like `general-purpose`, `Explore`, `Plan`, or custom agent names from `.claude/agents/`).
1344 1602
1345#### SubagentStart input1603#### SubagentStart input
1346 1604
1347In addition to the [common input fields](#common-input-fields), SubagentStart hooks receive `agent_id` with the unique identifier for the subagent and `agent_type` with the agent name (built-in agents like `"Bash"`, `"Explore"`, `"Plan"`, or custom agent names).1605In addition to the [common input fields](#common-input-fields), SubagentStart hooks receive `agent_id` with the unique identifier for the subagent and `agent_type` with the agent name (built-in agents like `"general-purpose"`, `"Explore"`, `"Plan"`, or custom agent names).
1348 1606
1349```json theme={null}1607```json theme={null}
1350{1608{
1360SubagentStart hooks cannot block subagent creation, but they can inject context into the subagent. In addition to the [JSON output fields](#json-output) available to all hooks, you can return:1618SubagentStart hooks cannot block subagent creation, but they can inject context into the subagent. In addition to the [JSON output fields](#json-output) available to all hooks, you can return:
1361 1619
1362| Field | Description |1620| Field | Description |
1363| :------------------ | :------------------------------------- |1621| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------ |
1364| `additionalContext` | String added to the subagent's context |1622| `additionalContext` | String added to the subagent's context at the start of its conversation, before its first prompt. See [Add context for Claude](#add-context-for-claude) |
1365 1623
1366```json theme={null}1624```json theme={null}
1367{1625{
1556 1814
1557| Field | Description |1815| Field | Description |
1558| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |1816| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1559| `error` | Error type: `rate_limit`, `authentication_failed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, or `unknown` |1817| `error` | Error type: `rate_limit`, `authentication_failed`, `oauth_org_not_allowed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, or `unknown` |
1560| `error_details` | Additional details about the error, when available |1818| `error_details` | Additional details about the error, when available |
1561| `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"` |1819| `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"` |
1562 1820
1693 1951
1694Runs when the working directory changes during a session, for example when Claude executes a `cd` command. Use this to react to directory changes: reload environment variables, activate project-specific toolchains, or run setup scripts automatically. Pairs with [FileChanged](#filechanged) for tools like [direnv](https://direnv.net/) that manage per-directory environment.1952Runs when the working directory changes during a session, for example when Claude executes a `cd` command. Use this to react to directory changes: reload environment variables, activate project-specific toolchains, or run setup scripts automatically. Pairs with [FileChanged](#filechanged) for tools like [direnv](https://direnv.net/) that manage per-directory environment.
1695 1953
1696CwdChanged hooks have access to `CLAUDE_ENV_FILE`. Variables written to that file persist into subsequent Bash commands for the session, just as in [SessionStart hooks](#persist-environment-variables). Only `type: "command"` hooks are supported.1954CwdChanged hooks have access to `CLAUDE_ENV_FILE`. Variables written to that file persist into subsequent Bash commands for the session, just as in [SessionStart hooks](#persist-environment-variables).
1697 1955
1698CwdChanged does not support matchers and fires on every directory change.1956CwdChanged does not support matchers and fires on every directory change.
1699 1957
1731* **Build the watch list**: the value is split on `|` and each segment is registered as a literal filename in the working directory, so `".envrc|.env"` watches exactly those two files. Regex patterns are not useful here: a value like `^\.env` would watch a file literally named `^\.env`.1989* **Build the watch list**: the value is split on `|` and each segment is registered as a literal filename in the working directory, so `".envrc|.env"` watches exactly those two files. Regex patterns are not useful here: a value like `^\.env` would watch a file literally named `^\.env`.
1732* **Filter which hooks run**: when a watched file changes, the same value filters which hook groups run using the standard [matcher rules](#matcher-patterns) against the changed file's basename.1990* **Filter which hooks run**: when a watched file changes, the same value filters which hook groups run using the standard [matcher rules](#matcher-patterns) against the changed file's basename.
1733 1991
1734FileChanged hooks have access to `CLAUDE_ENV_FILE`. Variables written to that file persist into subsequent Bash commands for the session, just as in [SessionStart hooks](#persist-environment-variables). Only `type: "command"` hooks are supported.1992FileChanged hooks have access to `CLAUDE_ENV_FILE`. Variables written to that file persist into subsequent Bash commands for the session, just as in [SessionStart hooks](#persist-environment-variables).
1735 1993
1736#### FileChanged input1994#### FileChanged input
1737 1995
1865| `manual` | `/compact` |2123| `manual` | `/compact` |
1866| `auto` | Auto-compact when the context window is full |2124| `auto` | Auto-compact when the context window is full |
1867 2125
2126Exit with code 2 to block compaction. For a manual `/compact`, the stderr message is shown to the user. You can also block by returning JSON with `"decision": "block"`.
2127
2128Blocking automatic compaction has different effects depending on when it fires. If compaction was triggered proactively before the context limit, Claude Code skips it and the conversation continues uncompacted. If compaction was triggered to recover from a context-limit error already returned by the API, the underlying error surfaces and the current request fails.
2129
1868#### PreCompact input2130#### PreCompact input
1869 2131
1870In addition to the [common input fields](#common-input-fields), PreCompact hooks receive `trigger` and `custom_instructions`. For `manual`, `custom_instructions` contains what the user passes into `/compact`. For `auto`, `custom_instructions` is empty.2132In addition to the [common input fields](#common-input-fields), PreCompact hooks receive `trigger` and `custom_instructions`. For `manual`, `custom_instructions` contains what the user passes into `/compact`. For `auto`, `custom_instructions` is empty.
1940 2202
1941SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.2203SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.
1942 2204
1943SessionEnd 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.2205SessionEnd hooks have a default timeout of 1.5 seconds. This applies to session exit, `/clear`, and switching sessions via interactive `/resume`. If a hook needs more time, set a per-hook `timeout` in the hook configuration. The overall budget is automatically raised to the highest per-hook timeout configured in settings files, up to 60 seconds. Timeouts set on plugin-provided hooks do not raise the budget. To override the budget explicitly, set the `CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS` environment variable in milliseconds.
1944 2206
1945```bash theme={null}2207```bash theme={null}
1946CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude2208CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude
2064 2326
2065## Prompt-based hooks2327## Prompt-based hooks
2066 2328
2067In 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.2329In addition to command, HTTP, and MCP tool 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.
2068 2330
2069Events that support all four hook types (`command`, `http`, `prompt`, and `agent`):2331Events that support all five hook types (`command`, `http`, `mcp_tool`, `prompt`, and `agent`):
2070 2332
2071* `PermissionRequest`2333* `PermissionRequest`
2334* `PostToolBatch`
2072* `PostToolUse`2335* `PostToolUse`
2073* `PostToolUseFailure`2336* `PostToolUseFailure`
2074* `PreToolUse`2337* `PreToolUse`
2076* `SubagentStop`2339* `SubagentStop`
2077* `TaskCompleted`2340* `TaskCompleted`
2078* `TaskCreated`2341* `TaskCreated`
2342* `UserPromptExpansion`
2079* `UserPromptSubmit`2343* `UserPromptSubmit`
2080 2344
2081Events that support `command` and `http` hooks but not `prompt` or `agent`:2345Events that support `command`, `http`, and `mcp_tool` hooks but not `prompt` or `agent`:
2082 2346
2083* `ConfigChange`2347* `ConfigChange`
2084* `CwdChanged`2348* `CwdChanged`
2097* `WorktreeCreate`2361* `WorktreeCreate`
2098* `WorktreeRemove`2362* `WorktreeRemove`
2099 2363
2100`SessionStart` supports only `command` hooks.2364`SessionStart` and `Setup` support `command` and `mcp_tool` hooks. They do not support `http`, `prompt`, or `agent` hooks.
2101 2365
2102### How prompt-based hooks work2366### How prompt-based hooks work
2103 2367
2177 2441
2178## Agent-based hooks2442## Agent-based hooks
2179 2443
2444<Warning>
2445 Agent hooks are experimental. Behavior and configuration may change in future releases. For production workflows, prefer [command hooks](#command-hook-fields).
2446</Warning>
2447
2180Agent-based hooks (`type: "agent"`) are like prompt-based hooks but with multi-turn tool access. Instead of a single LLM call, an agent hook spawns a subagent that can read files, search code, and inspect the codebase to verify conditions. Agent hooks support the same events as prompt-based hooks.2448Agent-based hooks (`type: "agent"`) are like prompt-based hooks but with multi-turn tool access. Instead of a single LLM call, an agent hook spawns a subagent that can read files, search code, and inspect the codebase to verify conditions. Agent hooks support the same events as prompt-based hooks.
2181 2449
2182### How agent hooks work2450### How agent hooks work
2319 2587
2320* Only `type: "command"` hooks support `async`. Prompt-based hooks cannot run asynchronously.2588* Only `type: "command"` hooks support `async`. Prompt-based hooks cannot run asynchronously.
2321* Async hooks cannot block tool calls or return decisions. By the time the hook completes, the triggering action has already proceeded.2589* Async hooks cannot block tool calls or return decisions. By the time the hook completes, the triggering action has already proceeded.
2322* Hook output is delivered on the next conversation turn. If the session is idle, the response waits until the next user interaction.2590* Hook output is delivered on the next conversation turn. If the session is idle, the response waits until the next user interaction. Exception: an `asyncRewake` hook that exits with code 2 wakes Claude immediately even when the session is idle.
2323* Each execution creates a separate background process. There is no deduplication across multiple firings of the same async hook.2591* Each execution creates a separate background process. There is no deduplication across multiple firings of the same async hook.
2324 2592
2325## Security considerations2593## Security considerations
2378 2646
2379For more granular hook matching details, set `CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose` to see additional log lines such as hook matcher counts and query matching.2647For more granular hook matching details, set `CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose` to see additional log lines such as hook matcher counts and query matching.
2380 2648
2381For troubleshooting common issues like hooks not firing, infinite Stop hook loops, or configuration errors, see [Limitations and troubleshooting](/en/hooks-guide#limitations-and-troubleshooting) in the guide.2649For troubleshooting common issues like hooks not firing, infinite Stop hook loops, or configuration errors, see [Limitations and troubleshooting](/en/hooks-guide#limitations-and-troubleshooting) in the guide. For a broader diagnostic walkthrough covering `/context`, `/doctor`, and settings precedence, see [Debug your config](/en/debug-your-config).