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/_SQ1BnFTP0QUrae-/images/hooks-lifecycle.svg?fit=max&auto=format&n=_SQ1BnFTP0QUrae-&q=85&s=75bd3d4bdefd4f08a7d736167243fd78" alt="Hook lifecycle diagram showing 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| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |30| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |
31| `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 |32| `PreToolUse` | Before a tool call executes. Can block it |
32| `PermissionRequest` | When a permission dialog appears |33| `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 |34| `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 |35| `PostToolUse` | After a tool call succeeds |
35| `PostToolUseFailure` | After a tool call fails |36| `PostToolUseFailure` | After a tool call fails |
37| `PostToolBatch` | After a full batch of parallel tool calls resolves, before the next model call |
36| `Notification` | When Claude Code sends a notification |38| `Notification` | When Claude Code sends a notification |
37| `SubagentStart` | When a subagent is spawned |39| `SubagentStart` | When a subagent is spawned |
38| `SubagentStop` | When a subagent finishes |40| `SubagentStop` | When a subagent finishes |
55 57
56### How a hook resolves58### How a hook resolves
57 59
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:60To 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 61
60```json theme={null}62```json theme={null}
61{63{
116 </Step>118 </Step>
117 119
118 <Step title="If condition checks">120 <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.121 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>122 </Step>
121 123
122 <Step title="Hook handler runs">124 <Step title="Hook handler runs">
153See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.155See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.
154 156
155<Note>157<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.158 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>159</Note>
158 160
159### Hook locations161### Hook locations
186Each event type matches on a different field:188Each event type matches on a different field:
187 189
188| Event | What the matcher filters | Example matcher values |190| Event | What the matcher filters | Example matcher values |
189| :------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ |191| :------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ |
190| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |192| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |
191| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |193| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |
192| `SessionEnd` | why the session ended | `clear`, `resume`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |194| `SessionEnd` | why the session ended | `clear`, `resume`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |
199| `FileChanged` | literal filenames to watch (see [FileChanged](#filechanged)) | `.envrc\|.env` |201| `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` |202| `StopFailure` | error type | `rate_limit`, `authentication_failed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, `unknown` |
201| `InstructionsLoaded` | load reason | `session_start`, `nested_traversal`, `path_glob_match`, `include`, `compact` |203| `InstructionsLoaded` | load reason | `session_start`, `nested_traversal`, `path_glob_match`, `include`, `compact` |
204| `UserPromptExpansion` | command name | your skill or command names |
202| `Elicitation` | MCP server name | your configured MCP server names |205| `Elicitation` | MCP server name | your configured MCP server names |
203| `ElicitationResult` | MCP server name | same values as `Elicitation` |206| `ElicitationResult` | MCP server name | same values as `Elicitation` |
204| `UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove` | no matcher support | always fires on every occurrence |207| `UserPromptSubmit`, `PostToolBatch`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove` | no matcher support | always fires on every occurrence |
205 208
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.209The 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 210
225}228}
226```229```
227 230
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.231`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 232
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.233For 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 234
232#### Match MCP tools235#### Match MCP tools
233 236
275 278
276### Hook handler fields279### Hook handler fields
277 280
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:281Each 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 282
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.283* **[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.284* **[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.
285* **[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).286* **[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).287* **[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 288
285#### Common fields289#### Common fields
286 290
287These fields apply to all hook types:291These fields apply to all hook types:
288 292
289| Field | Required | Description |293| Field | Required | Description |
290| :-------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |294| :-------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
291| `type` | yes | `"command"`, `"http"`, `"prompt"`, or `"agent"` |295| `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) |296| `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 |297| `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 |298| `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) |299| `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 |
300
301The `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 302
297#### Command hook fields303#### Command hook fields
298 304
344}350}
345```351```
346 352
353#### MCP tool hook fields
354
355In addition to the [common fields](#common-fields), MCP tool hooks accept these fields:
356
357| Field | Required | Description |
358| :------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
359| `server` | yes | Name of a configured MCP server. The server must already be connected; the hook never triggers an OAuth or connection flow |
360| `tool` | yes | Name of the tool to call on that server |
361| `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}"` |
362
363The 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.
364
365MCP 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.
366
367This example calls the `security_scan` tool on the `my_server` MCP server after each `Write` or `Edit`, passing the edited file's path:
368
369```json theme={null}
370{
371 "hooks": {
372 "PostToolUse": [
373 {
374 "matcher": "Write|Edit",
375 "hooks": [
376 {
377 "type": "mcp_tool",
378 "server": "my_server",
379 "tool": "security_scan",
380 "input": { "file_path": "${tool_input.file_path}" }
381 }
382 ]
383 }
384 ]
385 }
386}
387```
388
347#### Prompt and agent hook fields389#### Prompt and agent hook fields
348 390
349In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:391In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:
444 486
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.487Type `/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 488
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:489The 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 490
449* `User`: from `~/.claude/settings.json`491* `User`: from `~/.claude/settings.json`
450* `Project`: from `.claude/settings.json`492* `Project`: from `.claude/settings.json`
510 552
511The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.553The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.
512 554
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.555**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 556
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.557**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 558
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.582Exit 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 583
542| Hook event | Can block? | What happens on exit 2 |584| Hook event | Can block? | What happens on exit 2 |
543| :------------------- | :--------- | :----------------------------------------------------------------------------------------------------------------------------------- |585| :-------------------- | :--------- | :----------------------------------------------------------------------------------------------------------------------------------- |
544| `PreToolUse` | Yes | Blocks the tool call |586| `PreToolUse` | Yes | Blocks the tool call |
545| `PermissionRequest` | Yes | Denies the permission |587| `PermissionRequest` | Yes | Denies the permission |
546| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |588| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |
589| `UserPromptExpansion` | Yes | Blocks the expansion |
547| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |590| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |
548| `SubagentStop` | Yes | Prevents the subagent from stopping |591| `SubagentStop` | Yes | Prevents the subagent from stopping |
549| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |592| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |
553| `StopFailure` | No | Output and exit code are ignored |596| `StopFailure` | No | Output and exit code are ignored |
554| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |597| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |
555| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |598| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |
599| `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 |600| `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 |601| `Notification` | No | Shows stderr to user only |
558| `SubagentStart` | No | Shows stderr to user only |602| `SubagentStart` | No | Shows stderr to user only |
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:660Not 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 661
618| Events | Decision pattern | Key fields |662| Events | Decision pattern | Key fields |
619| :-------------------------------------------------------------------------------------------------------------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |663| :---------------------------------------------------------------------------------------------------------------------------------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
620| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange, PreCompact | Top-level `decision` | `decision: "block"`, `reason` |664| 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 |665| 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` |666| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask/defer), `permissionDecisionReason` |
623| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |667| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |
631 675
632<Tabs>676<Tabs>
633 <Tab title="Top-level decision">677 <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:678 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 679
636 ```json theme={null}680 ```json theme={null}
637 {681 {
684 728
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.729Runs 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 730
687SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` hooks are supported.731SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` and `type: "mcp_tool"` hooks are supported.
688 732
689The matcher value corresponds to how the session was initiated:733The matcher value corresponds to how the session was initiated:
690 734
863 block prompts or want more structured control.907 block prompts or want more structured control.
864</Note>908</Note>
865 909
910### UserPromptExpansion
911
912Runs 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`.
913
914This 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.
915
916Matches on `command_name`. Leave the matcher empty to fire on every prompt-type slash command.
917
918#### UserPromptExpansion input
919
920In 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.
921
922```json theme={null}
923{
924 "session_id": "abc123",
925 "transcript_path": "/Users/.../00893aaf.jsonl",
926 "cwd": "/Users/...",
927 "permission_mode": "default",
928 "hook_event_name": "UserPromptExpansion",
929 "expansion_type": "slash_command",
930 "command_name": "example-skill",
931 "command_args": "arg1 arg2",
932 "command_source": "plugin",
933 "prompt": "/example-skill arg1 arg2"
934}
935```
936
937#### UserPromptExpansion decision control
938
939`UserPromptExpansion` hooks can block the expansion or add context. All [JSON output fields](#json-output) are available.
940
941| Field | Description |
942| :------------------ | :------------------------------------------------------------------------------- |
943| `decision` | `"block"` prevents the slash command from expanding. Omit to allow it to proceed |
944| `reason` | Shown to the user when `decision` is `"block"` |
945| `additionalContext` | String added to Claude's context alongside the expanded prompt |
946
947```json theme={null}
948{
949 "decision": "block",
950 "reason": "This slash command is not available",
951 "hookSpecificOutput": {
952 "hookEventName": "UserPromptExpansion",
953 "additionalContext": "Additional context for this expansion"
954 }
955}
956```
957
866### PreToolUse958### PreToolUse
867 959
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).960Runs 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).
1042}1134}
1043```1135```
1044 1136
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.1137There 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 1138
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.1139`"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 1140
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:1183`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 1184
1093| Field | Description |1185| Field | Description |
1094| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |1186| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1095| `behavior` | `"allow"` grants the permission, `"deny"` denies it |1187| `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 |1188| `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 |1189| `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 |1190| `message` | For `"deny"` only: tells Claude why the permission was denied |
1099| `interrupt` | For `"deny"` only: if `true`, stops Claude |1191| `interrupt` | For `"deny"` only: if `true`, stops Claude |
1125| `addDirectories` | `directories`, `destination` | Adds working directories. `directories` is an array of path strings |1217| `addDirectories` | `directories`, `destination` | Adds working directories. `directories` is an array of path strings |
1126| `removeDirectories` | `directories`, `destination` | Removes working directories |1218| `removeDirectories` | `directories`, `destination` | Removes working directories |
1127 1219
1220<Note>
1221 `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`.
1222</Note>
1223
1128The `destination` field on every entry determines whether the change stays in memory or persists to a settings file.1224The `destination` field on every entry determines whether the change stays in memory or persists to a settings file.
1129 1225
1130| `destination` | Writes to |1226| `destination` | Writes to |
1162 "filePath": "/path/to/file.txt",1258 "filePath": "/path/to/file.txt",
1163 "success": true1259 "success": true
1164 },1260 },
1165 "tool_use_id": "toolu_01ABC123..."1261 "tool_use_id": "toolu_01ABC123...",
1262 "duration_ms": 12
1166}1263}
1167```1264```
1168 1265
1266| Field | Description |
1267| :------------ | :------------------------------------------------------------------------------------------------------------ |
1268| `duration_ms` | Optional. Tool execution time in milliseconds. Excludes time spent in permission prompts and PreToolUse hooks |
1269
1169#### PostToolUse decision control1270#### PostToolUse decision control
1170 1271
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:1272`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:
1212 },1313 },
1213 "tool_use_id": "toolu_01ABC123...",1314 "tool_use_id": "toolu_01ABC123...",
1214 "error": "Command exited with non-zero status code 1",1315 "error": "Command exited with non-zero status code 1",
1215 "is_interrupt": false1316 "is_interrupt": false,
1317 "duration_ms": 4187
1216}1318}
1217```1319```
1218 1320
1219| Field | Description |1321| Field | Description |
1220| :------------- | :------------------------------------------------------------------------------ |1322| :------------- | :------------------------------------------------------------------------------------------------------------ |
1221| `error` | String describing what went wrong |1323| `error` | String describing what went wrong |
1222| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |1324| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |
1325| `duration_ms` | Optional. Tool execution time in milliseconds. Excludes time spent in permission prompts and PreToolUse hooks |
1223 1326
1224#### PostToolUseFailure decision control1327#### PostToolUseFailure decision control
1225 1328
1238}1341}
1239```1342```
1240 1343
1344### PostToolBatch
1345
1346Runs 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.
1347
1348#### PostToolBatch input
1349
1350In addition to the [common input fields](#common-input-fields), PostToolBatch hooks receive `tool_calls`, an array describing every tool call in the batch:
1351
1352```json theme={null}
1353{
1354 "session_id": "abc123",
1355 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1356 "cwd": "/Users/...",
1357 "permission_mode": "default",
1358 "hook_event_name": "PostToolBatch",
1359 "tool_calls": [
1360 {
1361 "tool_name": "Read",
1362 "tool_input": {"file_path": "/.../ledger/accounts.py"},
1363 "tool_use_id": "toolu_01...",
1364 "tool_response": " 1\tfrom __future__ import annotations\n 2\t..."
1365 },
1366 {
1367 "tool_name": "Read",
1368 "tool_input": {"file_path": "/.../ledger/transactions.py"},
1369 "tool_use_id": "toolu_02...",
1370 "tool_response": " 1\tfrom __future__ import annotations\n 2\t..."
1371 }
1372 ]
1373}
1374```
1375
1376`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.
1377
1378<Note>
1379 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.
1380</Note>
1381
1382#### PostToolBatch decision control
1383
1384`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:
1385
1386| Field | Description |
1387| :------------------ | :------------------------------------------------------ |
1388| `additionalContext` | Context string injected once before the next model call |
1389
1390```json theme={null}
1391{
1392 "hookSpecificOutput": {
1393 "hookEventName": "PostToolBatch",
1394 "additionalContext": "These files are part of the ledger module. Run pytest before marking the task complete."
1395 }
1396}
1397```
1398
1399<Note>
1400 Injected `additionalContext` is persisted to the session transcript. On `--continue` or `--resume`, the saved text is replayed from disk and the hook does not re-run for past turns. Prefer static context such as conventions or file-type guidance over dynamic values like timestamps or the current commit SHA, since those become stale on resume.
1401
1402 Frame the context as factual information rather than imperative system instructions. Text written as out-of-band system commands can trigger Claude's prompt-injection defenses, which surfaces the injection to the user instead of acting on it.
1403</Note>
1404
1405Returning `decision: "block"` or `continue: false` stops the agentic loop before the next model call.
1406
1241### PermissionDenied1407### PermissionDenied
1242 1408
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.1409Runs 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.
1694 1860
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.1861Runs 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 1862
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.1863CwdChanged 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 1864
1699CwdChanged does not support matchers and fires on every directory change.1865CwdChanged does not support matchers and fires on every directory change.
1700 1866
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`.1898* **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.1899* **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 1900
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.1901FileChanged 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 1902
1737#### FileChanged input1903#### FileChanged input
1738 1904
2069 2235
2070## Prompt-based hooks2236## Prompt-based hooks
2071 2237
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.2238In 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 2239
2074Events that support all four hook types (`command`, `http`, `prompt`, and `agent`):2240Events that support all five hook types (`command`, `http`, `mcp_tool`, `prompt`, and `agent`):
2075 2241
2076* `PermissionRequest`2242* `PermissionRequest`
2243* `PostToolBatch`
2077* `PostToolUse`2244* `PostToolUse`
2078* `PostToolUseFailure`2245* `PostToolUseFailure`
2079* `PreToolUse`2246* `PreToolUse`
2081* `SubagentStop`2248* `SubagentStop`
2082* `TaskCompleted`2249* `TaskCompleted`
2083* `TaskCreated`2250* `TaskCreated`
2251* `UserPromptExpansion`
2084* `UserPromptSubmit`2252* `UserPromptSubmit`
2085 2253
2086Events that support `command` and `http` hooks but not `prompt` or `agent`:2254Events that support `command`, `http`, and `mcp_tool` hooks but not `prompt` or `agent`:
2087 2255
2088* `ConfigChange`2256* `ConfigChange`
2089* `CwdChanged`2257* `CwdChanged`
2102* `WorktreeCreate`2270* `WorktreeCreate`
2103* `WorktreeRemove`2271* `WorktreeRemove`
2104 2272
2105`SessionStart` supports only `command` hooks.2273`SessionStart` and `Setup` support `command` and `mcp_tool` hooks. They do not support `http`, `prompt`, or `agent` hooks.
2106 2274
2107### How prompt-based hooks work2275### How prompt-based hooks work
2108 2276
2182 2350
2183## Agent-based hooks2351## Agent-based hooks
2184 2352
2353<Warning>
2354 Agent hooks are experimental. Behavior and configuration may change in future releases. For production workflows, prefer [command hooks](#command-hook-fields).
2355</Warning>
2356
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.2357Agent-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 2358
2187### How agent hooks work2359### How agent hooks work
2383 2555
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.2556For 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 2557
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.2558For 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).