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/UMJp-WgTWngzO609/images/hooks-lifecycle.svg?fit=max&auto=format&n=UMJp-WgTWngzO609&q=85&s=3f4de67df216c87dc313943b32c15f62" alt="Hook lifecycle diagram showing SessionStart, then a per-turn loop containing UserPromptSubmit, the nested agentic loop (PreToolUse, PermissionRequest, PostToolUse, 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="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
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
344}352}
345```353```
346 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
347#### Prompt and agent hook fields391#### Prompt and agent hook fields
348 392
349In 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:
444 488
445Type `/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.
446 490
447The 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:
448 492
449* `User`: from `~/.claude/settings.json`493* `User`: from `~/.claude/settings.json`
450* `Project`: from `.claude/settings.json`494* `Project`: from `.claude/settings.json`
474Hook 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.518Hook 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.
475 519
476| Field | Description |520| Field | Description |
477| :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |521| :---------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
478| `session_id` | Current session identifier |522| `session_id` | Current session identifier |
479| `transcript_path` | Path to conversation JSON |523| `transcript_path` | Path to conversation JSON |
480| `cwd` | Current working directory when the hook is invoked |524| `cwd` | Current working directory when the hook is invoked |
481| `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 |525| `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 |
526| `effort` | Object with a `level` field holding the active [effort level](/en/model-config#adjust-effort-level) for the turn: `"low"`, `"medium"`, `"high"`, `"xhigh"`, or `"max"`. If the requested effort exceeds what the current model supports, this is the downgraded level the model actually used, not the level you requested. The object matches the [status line](/en/statusline#available-data) `effort` field. Present for events that fire within a tool-use context, such as `PreToolUse`, `PostToolUse`, `Stop`, and `SubagentStop`, when the current model supports the effort parameter. The level is also available to hook commands and the Bash tool as the `$CLAUDE_EFFORT` environment variable. |
482| `hook_event_name` | Name of the event that fired |527| `hook_event_name` | Name of the event that fired |
483 528
484When running with `--agent` or inside a subagent, two additional fields are included:529When running with `--agent` or inside a subagent, two additional fields are included:
510 555
511The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.556The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.
512 557
513**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.558**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.
514 559
515**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.560**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.
516 561
540Exit 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.585Exit 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.
541 586
542| Hook event | Can block? | What happens on exit 2 |587| Hook event | Can block? | What happens on exit 2 |
543| :------------------- | :--------- | :----------------------------------------------------------------------------------------------------------------------------------- |588| :-------------------- | :--------- | :----------------------------------------------------------------------------------------------------------------------------------- |
544| `PreToolUse` | Yes | Blocks the tool call |589| `PreToolUse` | Yes | Blocks the tool call |
545| `PermissionRequest` | Yes | Denies the permission |590| `PermissionRequest` | Yes | Denies the permission |
546| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |591| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |
592| `UserPromptExpansion` | Yes | Blocks the expansion |
547| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |593| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |
548| `SubagentStop` | Yes | Prevents the subagent from stopping |594| `SubagentStop` | Yes | Prevents the subagent from stopping |
549| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |595| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |
553| `StopFailure` | No | Output and exit code are ignored |599| `StopFailure` | No | Output and exit code are ignored |
554| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |600| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |
555| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |601| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |
602| `PostToolBatch` | Yes | Stops the agentic loop before the next model call |
556| `PermissionDenied` | No | Exit code and stderr are ignored (denial already occurred). Use JSON `hookSpecificOutput.retry: true` to tell the model it may retry |603| `PermissionDenied` | No | Exit code and stderr are ignored (denial already occurred). Use JSON `hookSpecificOutput.retry: true` to tell the model it may retry |
557| `Notification` | No | Shows stderr to user only |604| `Notification` | No | Shows stderr to user only |
558| `SubagentStart` | No | Shows stderr to user only |605| `SubagentStart` | No | Shows stderr to user only |
559| `SessionStart` | No | Shows stderr to user only |606| `SessionStart` | No | Shows stderr to user only |
607| `Setup` | No | Shows stderr to user only |
560| `SessionEnd` | No | Shows stderr to user only |608| `SessionEnd` | No | Shows stderr to user only |
561| `CwdChanged` | No | Shows stderr to user only |609| `CwdChanged` | No | Shows stderr to user only |
562| `FileChanged` | No | Shows stderr to user only |610| `FileChanged` | No | Shows stderr to user only |
611{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }659{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
612```660```
613 661
662#### Add context for Claude
663
664The `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.
665
666Return `additionalContext` inside `hookSpecificOutput` alongside the event name:
667
668```json theme={null}
669{
670 "hookSpecificOutput": {
671 "hookEventName": "PostToolUse",
672 "additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
673 }
674}
675```
676
677Where the reminder appears depends on the event:
678
679* [SessionStart](#sessionstart), [Setup](#setup), and [SubagentStart](#subagentstart): at the start of the conversation, before the first prompt
680* [UserPromptSubmit](#userpromptsubmit) and [UserPromptExpansion](#userpromptexpansion): alongside the submitted prompt
681* [PreToolUse](#pretooluse), [PostToolUse](#posttooluse), [PostToolUseFailure](#posttoolusefailure), and [PostToolBatch](#posttoolbatch): next to the tool result
682
683When 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.
684
685Use `additionalContext` for information Claude should know about the current state of your environment or the operation that just ran:
686
687* **Environment state**: the current branch, deployment target, or active feature flags
688* **Conditional project rules**: which test command applies to the file just edited, which directories are read-only in this worktree
689* **External data**: open issues assigned to you, recent CI results, content fetched from an internal service
690
691For instructions that never change, prefer [CLAUDE.md](/en/memory). It loads without running a script and is the standard place for static project conventions.
692
693Write 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.
694
695Once 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.
696
614#### Decision control697#### Decision control
615 698
616Not 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:699Not 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:
617 700
618| Events | Decision pattern | Key fields |701| Events | Decision pattern | Key fields |
619| :-------------------------------------------------------------------------------------------------------------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |702| :---------------------------------------------------------------------------------------------------------------------------------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
620| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange, PreCompact | Top-level `decision` | `decision: "block"`, `reason` |703| UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact | Top-level `decision` | `decision: "block"`, `reason` |
621| 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 |704| 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 |
622| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask/defer), `permissionDecisionReason` |705| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask/defer), `permissionDecisionReason` |
623| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |706| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |
631 714
632<Tabs>715<Tabs>
633 <Tab title="Top-level decision">716 <Tab title="Top-level decision">
634 Used by `UserPromptSubmit`, `PostToolUse`, `PostToolUseFailure`, `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:717 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:
635 718
636 ```json theme={null}719 ```json theme={null}
637 {720 {
684 767
685Runs 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.768Runs 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.
686 769
687SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` hooks are supported.770SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` and `type: "mcp_tool"` hooks are supported.
688 771
689The matcher value corresponds to how the session was initiated:772The matcher value corresponds to how the session was initiated:
690 773
715Any 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:798Any 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:
716 799
717| Field | Description |800| Field | Description |
718| :------------------ | :------------------------------------------------------------------------ |801| :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
719| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |802| `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 |
720 803
721```json theme={null}804```json theme={null}
722{805{
723 "hookSpecificOutput": {806 "hookSpecificOutput": {
724 "hookEventName": "SessionStart",807 "hookEventName": "SessionStart",
725 "additionalContext": "My additional context here"808 "additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2"
726 }809 }
727}810}
728```811```
729 812
813Since 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`.
814
730#### Persist environment variables815#### Persist environment variables
731 816
732SessionStart 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.817SessionStart 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.
767Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.852Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.
768 853
769<Note>854<Note>
770 `CLAUDE_ENV_FILE` is available for SessionStart, [CwdChanged](#cwdchanged), and [FileChanged](#filechanged) hooks. Other hook types do not have access to this variable.855 `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.
771</Note>856</Note>
772 857
858### Setup
859
860Fires 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.
861
862The matcher value corresponds to the CLI flag that triggered the hook:
863
864| Matcher | When it fires |
865| :------------ | :----------------------------------------- |
866| `init` | `claude --init-only` or `claude -p --init` |
867| `maintenance` | `claude -p --maintenance` |
868
869`--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.
870
871Because 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.
872
873#### Setup input
874
875In addition to the [common input fields](#common-input-fields), Setup hooks receive a `trigger` field set to either `"init"` or `"maintenance"`:
876
877```json theme={null}
878{
879 "session_id": "abc123",
880 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
881 "cwd": "/Users/...",
882 "hook_event_name": "Setup",
883 "trigger": "init"
884}
885```
886
887#### Setup decision control
888
889Setup 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:
890
891| Field | Description |
892| :------------------ | :------------------------------------------------------------------------ |
893| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |
894
895```json theme={null}
896{
897 "hookSpecificOutput": {
898 "hookEventName": "Setup",
899 "additionalContext": "Dependencies installed: node_modules, .venv"
900 }
901}
902```
903
904Setup 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.
905
773### InstructionsLoaded906### InstructionsLoaded
774 907
775Fires 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.908Fires 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.
840To block a prompt, return a JSON object with `decision` set to `"block"`:973To block a prompt, return a JSON object with `decision` set to `"block"`:
841 974
842| Field | Description |975| Field | Description |
843| :------------------ | :----------------------------------------------------------------------------------------------------------------- |976| :------------------ | :--------------------------------------------------------------------------------------------------------------------- |
844| `decision` | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |977| `decision` | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |
845| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |978| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |
846| `additionalContext` | String added to Claude's context |979| `additionalContext` | String added to Claude's context alongside the submitted prompt. See [Add context for Claude](#add-context-for-claude) |
847| `sessionTitle` | Sets the session title, same effect as `/rename`. Use to name sessions automatically based on the prompt content |980| `sessionTitle` | Sets the session title. Use to name sessions automatically based on the prompt content |
848 981
849```json theme={null}982```json theme={null}
850{983{
863 block prompts or want more structured control.996 block prompts or want more structured control.
864</Note>997</Note>
865 998
999### UserPromptExpansion
1000
1001Runs 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`.
1002
1003This 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.
1004
1005Matches on `command_name`. Leave the matcher empty to fire on every prompt-type slash command.
1006
1007#### UserPromptExpansion input
1008
1009In 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.
1010
1011```json theme={null}
1012{
1013 "session_id": "abc123",
1014 "transcript_path": "/Users/.../00893aaf.jsonl",
1015 "cwd": "/Users/...",
1016 "permission_mode": "default",
1017 "hook_event_name": "UserPromptExpansion",
1018 "expansion_type": "slash_command",
1019 "command_name": "example-skill",
1020 "command_args": "arg1 arg2",
1021 "command_source": "plugin",
1022 "prompt": "/example-skill arg1 arg2"
1023}
1024```
1025
1026#### UserPromptExpansion decision control
1027
1028`UserPromptExpansion` hooks can block the expansion or add context. All [JSON output fields](#json-output) are available.
1029
1030| Field | Description |
1031| :------------------ | :-------------------------------------------------------------------------------------------------------------------- |
1032| `decision` | `"block"` prevents the slash command from expanding. Omit to allow it to proceed |
1033| `reason` | Shown to the user when `decision` is `"block"` |
1034| `additionalContext` | String added to Claude's context alongside the expanded prompt. See [Add context for Claude](#add-context-for-claude) |
1035
1036```json theme={null}
1037{
1038 "decision": "block",
1039 "reason": "This slash command is not available",
1040 "hookSpecificOutput": {
1041 "hookEventName": "UserPromptExpansion",
1042 "additionalContext": "Additional context for this expansion"
1043 }
1044}
1045```
1046
866### PreToolUse1047### PreToolUse
867 1048
868Runs 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).1049Runs 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).
984| `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 |1165| `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 |
985| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude. For `"defer"`, ignored |1166| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude. For `"defer"`, ignored |
986| `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 |1167| `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 |
987| `additionalContext` | String added to Claude's context before the tool executes. For `"defer"`, ignored |1168| `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) |
988 1169
989When multiple PreToolUse hooks return different decisions, precedence is `deny` > `defer` > `ask` > `allow`.1170When multiple PreToolUse hooks return different decisions, precedence is `deny` > `defer` > `ask` > `allow`.
990 1171
1042}1223}
1043```1224```
1044 1225
1045There 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.1226There 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.
1046 1227
1047`"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.1228`"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.
1048 1229
1049If the deferred tool is no longer available when you resume, the process exits with `stop_reason: "tool_deferred_unavailable"` and `is_error: true` before the hook fires. This happens when an MCP server that provided the tool is not connected for the resumed session. The `deferred_tool_use` payload is still included so you can identify which tool went missing.1230If the deferred tool is no longer available when you resume, the process exits with `stop_reason: "tool_deferred_unavailable"` and `is_error: true` before the hook fires. This happens when an MCP server that provided the tool is not connected for the resumed session. The `deferred_tool_use` payload is still included so you can identify which tool went missing.
1050 1231
1051<Warning>1232<Note>
1052 `--resume` does not restore the permission mode from the prior session. Pass the same `--permission-mode` flag on resume that was active when the tool was deferred. Claude Code logs a warning if the modes differ.1233 `--resume` restores the permission mode that was active when the tool was deferred, so you do not need to pass `--permission-mode` again. The exceptions are `plan` and `bypassPermissions`, which are never carried over. Passing `--permission-mode` explicitly on resume overrides the restored value.
1053</Warning>1234</Note>
1054 1235
1055### PermissionRequest1236### PermissionRequest
1056 1237
1091`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:1272`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:
1092 1273
1093| Field | Description |1274| Field | Description |
1094| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |1275| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1095| `behavior` | `"allow"` grants the permission, `"deny"` denies it |1276| `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 |
1096| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones |1277| `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 |
1097| `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 |1278| `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 |
1098| `message` | For `"deny"` only: tells Claude why the permission was denied |1279| `message` | For `"deny"` only: tells Claude why the permission was denied |
1099| `interrupt` | For `"deny"` only: if `true`, stops Claude |1280| `interrupt` | For `"deny"` only: if `true`, stops Claude |
1125| `addDirectories` | `directories`, `destination` | Adds working directories. `directories` is an array of path strings |1306| `addDirectories` | `directories`, `destination` | Adds working directories. `directories` is an array of path strings |
1126| `removeDirectories` | `directories`, `destination` | Removes working directories |1307| `removeDirectories` | `directories`, `destination` | Removes working directories |
1127 1308
1309<Note>
1310 `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`.
1311</Note>
1312
1128The `destination` field on every entry determines whether the change stays in memory or persists to a settings file.1313The `destination` field on every entry determines whether the change stays in memory or persists to a settings file.
1129 1314
1130| `destination` | Writes to |1315| `destination` | Writes to |
1162 "filePath": "/path/to/file.txt",1347 "filePath": "/path/to/file.txt",
1163 "success": true1348 "success": true
1164 },1349 },
1165 "tool_use_id": "toolu_01ABC123..."1350 "tool_use_id": "toolu_01ABC123...",
1351 "duration_ms": 12
1166}1352}
1167```1353```
1168 1354
1355| Field | Description |
1356| :------------ | :------------------------------------------------------------------------------------------------------------ |
1357| `duration_ms` | Optional. Tool execution time in milliseconds. Excludes time spent in permission prompts and PreToolUse hooks |
1358
1169#### PostToolUse decision control1359#### PostToolUse decision control
1170 1360
1171`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:1361`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:
1172 1362
1173| Field | Description |1363| Field | Description |
1174| :--------------------- | :----------------------------------------------------------------------------------------- |1364| :--------------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
1175| `decision` | `"block"` prompts Claude with the `reason`. Omit to allow the action to proceed |1365| `decision` | `"block"` adds the `reason` next to the tool result. Claude still sees the original output; to replace it, use `updatedToolOutput` |
1176| `reason` | Explanation shown to Claude when `decision` is `"block"` |1366| `reason` | Explanation shown to Claude when `decision` is `"block"` |
1177| `additionalContext` | Additional context for Claude to consider |1367| `additionalContext` | String added to Claude's context alongside the tool result. See [Add context for Claude](#add-context-for-claude) |
1178| `updatedMCPToolOutput` | For [MCP tools](#match-mcp-tools) only: replaces the tool's output with the provided value |1368| `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 |
1369| `updatedMCPToolOutput` | Replaces the output for [MCP tools](#match-mcp-tools) only. Prefer `updatedToolOutput`, which works for all tools |
1370
1371The example below replaces the output of a `Bash` call. The replacement value matches the `Bash` tool's output shape:
1179 1372
1180```json theme={null}1373```json theme={null}
1181{1374{
1182 "decision": "block",
1183 "reason": "Explanation for decision",
1184 "hookSpecificOutput": {1375 "hookSpecificOutput": {
1185 "hookEventName": "PostToolUse",1376 "hookEventName": "PostToolUse",
1186 "additionalContext": "Additional information for Claude"1377 "additionalContext": "Additional information for Claude",
1378 "updatedToolOutput": {
1379 "stdout": "[redacted]",
1380 "stderr": "",
1381 "interrupted": false,
1382 "isImage": false
1383 }
1187 }1384 }
1188}1385}
1189```1386```
1190 1387
1388<Warning>
1389 `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.
1390
1391 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.
1392</Warning>
1393
1191### PostToolUseFailure1394### PostToolUseFailure
1192 1395
1193Runs 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.1396Runs 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.
1212 },1415 },
1213 "tool_use_id": "toolu_01ABC123...",1416 "tool_use_id": "toolu_01ABC123...",
1214 "error": "Command exited with non-zero status code 1",1417 "error": "Command exited with non-zero status code 1",
1215 "is_interrupt": false1418 "is_interrupt": false,
1419 "duration_ms": 4187
1216}1420}
1217```1421```
1218 1422
1219| Field | Description |1423| Field | Description |
1220| :------------- | :------------------------------------------------------------------------------ |1424| :------------- | :------------------------------------------------------------------------------------------------------------ |
1221| `error` | String describing what went wrong |1425| `error` | String describing what went wrong |
1222| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |1426| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |
1427| `duration_ms` | Optional. Tool execution time in milliseconds. Excludes time spent in permission prompts and PreToolUse hooks |
1223 1428
1224#### PostToolUseFailure decision control1429#### PostToolUseFailure decision control
1225 1430
1226`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:1431`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:
1227 1432
1228| Field | Description |1433| Field | Description |
1229| :------------------ | :------------------------------------------------------------ |1434| :------------------ | :---------------------------------------------------------------------------------------------------------- |
1230| `additionalContext` | Additional context for Claude to consider alongside the error |1435| `additionalContext` | String added to Claude's context alongside the error. See [Add context for Claude](#add-context-for-claude) |
1231 1436
1232```json theme={null}1437```json theme={null}
1233{1438{
1238}1443}
1239```1444```
1240 1445
1446### PostToolBatch
1447
1448Runs 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.
1449
1450#### PostToolBatch input
1451
1452In addition to the [common input fields](#common-input-fields), PostToolBatch hooks receive `tool_calls`, an array describing every tool call in the batch:
1453
1454```json theme={null}
1455{
1456 "session_id": "abc123",
1457 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1458 "cwd": "/Users/...",
1459 "permission_mode": "default",
1460 "hook_event_name": "PostToolBatch",
1461 "tool_calls": [
1462 {
1463 "tool_name": "Read",
1464 "tool_input": {"file_path": "/.../ledger/accounts.py"},
1465 "tool_use_id": "toolu_01...",
1466 "tool_response": " 1\tfrom __future__ import annotations\n 2\t..."
1467 },
1468 {
1469 "tool_name": "Read",
1470 "tool_input": {"file_path": "/.../ledger/transactions.py"},
1471 "tool_use_id": "toolu_02...",
1472 "tool_response": " 1\tfrom __future__ import annotations\n 2\t..."
1473 }
1474 ]
1475}
1476```
1477
1478`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.
1479
1480<Note>
1481 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.
1482</Note>
1483
1484#### PostToolBatch decision control
1485
1486`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:
1487
1488| Field | Description |
1489| :------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1490| `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 |
1491
1492```json theme={null}
1493{
1494 "hookSpecificOutput": {
1495 "hookEventName": "PostToolBatch",
1496 "additionalContext": "These files are part of the ledger module. Run pytest before marking the task complete."
1497 }
1498}
1499```
1500
1501Returning `decision: "block"` or `continue: false` stops the agentic loop before the next model call.
1502
1241### PermissionDenied1503### PermissionDenied
1242 1504
1243Runs 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.1505Runs 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.
1286 1548
1287### Notification1549### Notification
1288 1550
1289Runs 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.1551Runs 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.
1290 1552
1291Use 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:1553Use 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:
1292 1554
1333}1595}
1334```1596```
1335 1597
1336Notification 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:1598Notification 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.
1337
1338| Field | Description |
1339| :------------------ | :------------------------------- |
1340| `additionalContext` | String added to Claude's context |
1341 1599
1342### SubagentStart1600### SubagentStart
1343 1601
1344Runs 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/`).1602Runs 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/`).
1345 1603
1346#### SubagentStart input1604#### SubagentStart input
1347 1605
1348In 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).1606In 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).
1349 1607
1350```json theme={null}1608```json theme={null}
1351{1609{
1361SubagentStart 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:1619SubagentStart 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:
1362 1620
1363| Field | Description |1621| Field | Description |
1364| :------------------ | :------------------------------------- |1622| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------ |
1365| `additionalContext` | String added to the subagent's context |1623| `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) |
1366 1624
1367```json theme={null}1625```json theme={null}
1368{1626{
1557 1815
1558| Field | Description |1816| Field | Description |
1559| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |1817| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1560| `error` | Error type: `rate_limit`, `authentication_failed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, or `unknown` |1818| `error` | Error type: `rate_limit`, `authentication_failed`, `oauth_org_not_allowed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, or `unknown` |
1561| `error_details` | Additional details about the error, when available |1819| `error_details` | Additional details about the error, when available |
1562| `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"` |1820| `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"` |
1563 1821
1694 1952
1695Runs 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.1953Runs 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.
1696 1954
1697CwdChanged 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.1955CwdChanged 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).
1698 1956
1699CwdChanged does not support matchers and fires on every directory change.1957CwdChanged does not support matchers and fires on every directory change.
1700 1958
1732* **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`.1990* **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`.
1733* **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.1991* **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.
1734 1992
1735FileChanged 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.1993FileChanged 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).
1736 1994
1737#### FileChanged input1995#### FileChanged input
1738 1996
1768 2026
1769When 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.2027When 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.
1770 2028
1771Because the hook replaces the default behavior entirely, [`.worktreeinclude`](/en/common-workflows#copy-gitignored-files-to-worktrees) is not processed. If you need to copy local configuration files like `.env` into the new worktree, do it inside your hook script.2029Because the hook replaces the default behavior entirely, [`.worktreeinclude`](/en/worktrees#copy-gitignored-files-into-worktrees) is not processed. If you need to copy local configuration files like `.env` into the new worktree, do it inside your hook script.
1772 2030
1773The hook must return the absolute path to the created worktree directory. Claude Code uses this path as the working directory for the isolated session. Command hooks print it on stdout; HTTP hooks return it via `hookSpecificOutput.worktreePath`.2031The hook must return the absolute path to the created worktree directory. Claude Code uses this path as the working directory for the isolated session. Command hooks print it on stdout; HTTP hooks return it via `hookSpecificOutput.worktreePath`.
1774 2032
2069 2327
2070## Prompt-based hooks2328## Prompt-based hooks
2071 2329
2072In 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.2330In 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.
2073 2331
2074Events that support all four hook types (`command`, `http`, `prompt`, and `agent`):2332Events that support all five hook types (`command`, `http`, `mcp_tool`, `prompt`, and `agent`):
2075 2333
2076* `PermissionRequest`2334* `PermissionRequest`
2335* `PostToolBatch`
2077* `PostToolUse`2336* `PostToolUse`
2078* `PostToolUseFailure`2337* `PostToolUseFailure`
2079* `PreToolUse`2338* `PreToolUse`
2081* `SubagentStop`2340* `SubagentStop`
2082* `TaskCompleted`2341* `TaskCompleted`
2083* `TaskCreated`2342* `TaskCreated`
2343* `UserPromptExpansion`
2084* `UserPromptSubmit`2344* `UserPromptSubmit`
2085 2345
2086Events that support `command` and `http` hooks but not `prompt` or `agent`:2346Events that support `command`, `http`, and `mcp_tool` hooks but not `prompt` or `agent`:
2087 2347
2088* `ConfigChange`2348* `ConfigChange`
2089* `CwdChanged`2349* `CwdChanged`
2102* `WorktreeCreate`2362* `WorktreeCreate`
2103* `WorktreeRemove`2363* `WorktreeRemove`
2104 2364
2105`SessionStart` supports only `command` hooks.2365`SessionStart` and `Setup` support `command` and `mcp_tool` hooks. They do not support `http`, `prompt`, or `agent` hooks.
2106 2366
2107### How prompt-based hooks work2367### How prompt-based hooks work
2108 2368
2154```2414```
2155 2415
2156| Field | Description |2416| Field | Description |
2157| :------- | :--------------------------------------------------------- |2417| :------- | :------------------------------------------------------------------ |
2158| `ok` | `true` allows the action, `false` prevents it |2418| `ok` | `true` to allow, `false` to block. See the per-event behavior below |
2159| `reason` | Required when `ok` is `false`. Explanation shown to Claude |2419| `reason` | Required when `ok` is `false`. Explanation for the decision |
2420
2421What happens on `ok: false` depends on the event:
2422
2423* `Stop` and `SubagentStop`: the reason is fed back to Claude as its next instruction and the turn continues
2424* `PreToolUse`: the tool call is denied and the reason is returned to Claude as the tool error, equivalent to a command hook's `permissionDecision: "deny"`
2425* `PostToolUse`, `PostToolBatch`, `UserPromptSubmit`, and `UserPromptExpansion`: the turn ends and the reason appears in the chat as a warning line, equivalent to returning `"continue": false` from a command hook
2426* `PostToolUseFailure`, `TaskCreated`, and `TaskCompleted`: the reason is returned to Claude as a tool error, similar to `PreToolUse`
2427* `PermissionRequest`: `ok: false` has no effect. To deny an approval from a hook, use a [command hook](#command-hook-fields) returning `hookSpecificOutput.decision.behavior: "deny"`
2428
2429If you need finer control on any event, use a [command hook](#command-hook-fields) with the per-event fields described in [Decision control](#decision-control).
2160 2430
2161### Example: Multi-criteria Stop hook2431### Example: Multi-criteria Stop hook
2162 2432
2182 2452
2183## Agent-based hooks2453## Agent-based hooks
2184 2454
2455<Warning>
2456 Agent hooks are experimental. Behavior and configuration may change in future releases. For production workflows, prefer [command hooks](#command-hook-fields).
2457</Warning>
2458
2185Agent-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.2459Agent-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.
2186 2460
2187### How agent hooks work2461### How agent hooks work
2383 2657
2384For 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.2658For 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.
2385 2659
2386For 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.2660For 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).