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 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 |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 |
296 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.
304
297#### Command hook fields305#### Command hook fields
298 306
299In addition to the [common fields](#common-fields), command hooks accept these fields:307In addition to the [common fields](#common-fields), command hooks accept these fields:
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`
510 554
511The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.555The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.
512 556
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.557**Exit 0** means success. Claude Code parses stdout for [JSON output fields](#json-output). JSON output is only processed on exit 0. For most events, stdout is written to the debug log but not shown in the transcript. The exceptions are `UserPromptSubmit`, `UserPromptExpansion`, and `SessionStart`, where stdout is added as context that Claude can see and act on.
514 558
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.559**Exit 2** means a blocking error. Claude Code ignores stdout and any JSON in it. Instead, stderr text is fed back to Claude as an error message. The effect depends on the event: `PreToolUse` blocks the tool call, `UserPromptSubmit` rejects the prompt, and so on. See [exit code 2 behavior](#exit-code-2-behavior-per-event) for the full list.
516 560
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.584Exit code 2 is the way a hook signals "stop, don't do this." The effect depends on the event, because some events represent actions that can be blocked (like a tool call that hasn't happened yet) and others represent things that already happened or can't be prevented.
541 585
542| Hook event | Can block? | What happens on exit 2 |586| Hook event | Can block? | What happens on exit 2 |
543| :------------------- | :--------- | :----------------------------------------------------------------------------------------------------------------------------------- |587| :-------------------- | :--------- | :----------------------------------------------------------------------------------------------------------------------------------- |
544| `PreToolUse` | Yes | Blocks the tool call |588| `PreToolUse` | Yes | Blocks the tool call |
545| `PermissionRequest` | Yes | Denies the permission |589| `PermissionRequest` | Yes | Denies the permission |
546| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |590| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |
591| `UserPromptExpansion` | Yes | Blocks the expansion |
547| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |592| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |
548| `SubagentStop` | Yes | Prevents the subagent from stopping |593| `SubagentStop` | Yes | Prevents the subagent from stopping |
549| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |594| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |
553| `StopFailure` | No | Output and exit code are ignored |598| `StopFailure` | No | Output and exit code are ignored |
554| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |599| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |
555| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |600| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |
601| `PostToolBatch` | Yes | Stops the agentic loop before the next model call |
556| `PermissionDenied` | No | Exit code and stderr are ignored (denial already occurred). Use JSON `hookSpecificOutput.retry: true` to tell the model it may retry |602| `PermissionDenied` | No | Exit code and stderr are ignored (denial already occurred). Use JSON `hookSpecificOutput.retry: true` to tell the model it may retry |
557| `Notification` | No | Shows stderr to user only |603| `Notification` | No | Shows stderr to user only |
558| `SubagentStart` | No | Shows stderr to user only |604| `SubagentStart` | No | Shows stderr to user only |
559| `SessionStart` | No | Shows stderr to user only |605| `SessionStart` | No | Shows stderr to user only |
606| `Setup` | No | Shows stderr to user only |
560| `SessionEnd` | No | Shows stderr to user only |607| `SessionEnd` | No | Shows stderr to user only |
561| `CwdChanged` | No | Shows stderr to user only |608| `CwdChanged` | No | Shows stderr to user only |
562| `FileChanged` | No | Shows stderr to user only |609| `FileChanged` | No | Shows stderr to user only |
611{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }658{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
612```659```
613 660
661#### Add context for Claude
662
663The `additionalContext` field passes a string from your hook into Claude's context window. Claude Code wraps the string in a system reminder and inserts it into the conversation at the point where the hook fired. Claude reads the reminder on the next model request, but it does not appear as a chat message in the interface.
664
665Return `additionalContext` inside `hookSpecificOutput` alongside the event name:
666
667```json theme={null}
668{
669 "hookSpecificOutput": {
670 "hookEventName": "PostToolUse",
671 "additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
672 }
673}
674```
675
676Where the reminder appears depends on the event:
677
678* [SessionStart](#sessionstart), [Setup](#setup), and [SubagentStart](#subagentstart): at the start of the conversation, before the first prompt
679* [UserPromptSubmit](#userpromptsubmit) and [UserPromptExpansion](#userpromptexpansion): alongside the submitted prompt
680* [PreToolUse](#pretooluse), [PostToolUse](#posttooluse), [PostToolUseFailure](#posttoolusefailure), and [PostToolBatch](#posttoolbatch): next to the tool result
681
682When several hooks return `additionalContext` for the same event, Claude receives all of the values. If a value exceeds 10,000 characters, Claude Code writes the full text to a file in the session directory and passes Claude the file path with a short preview instead.
683
684Use `additionalContext` for information Claude should know about the current state of your environment or the operation that just ran:
685
686* **Environment state**: the current branch, deployment target, or active feature flags
687* **Conditional project rules**: which test command applies to the file just edited, which directories are read-only in this worktree
688* **External data**: open issues assigned to you, recent CI results, content fetched from an internal service
689
690For instructions that never change, prefer [CLAUDE.md](/en/memory). It loads without running a script and is the standard place for static project conventions.
691
692Write the text as factual statements rather than imperative system instructions. Phrasing such as "The deployment target is production" or "This repo uses `bun test`" reads as project information. Text framed as out-of-band system commands can trigger Claude's prompt-injection defenses, which causes Claude to surface the text to you instead of treating it as context.
693
694Once injected, the text is saved in the session transcript. For mid-session events like `PostToolUse` or `UserPromptSubmit`, resuming with `--continue` or `--resume` replays the saved text rather than re-running the hook for past turns, so values like timestamps or commit SHAs become stale on resume. `SessionStart` hooks run again on resume with `source` set to `"resume"`, so they can refresh their context.
695
614#### Decision control696#### Decision control
615 697
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:698Not every event supports blocking or controlling behavior through JSON. The events that do each use a different set of fields to express that decision. Use this table as a quick reference before writing a hook:
617 699
618| Events | Decision pattern | Key fields |700| Events | Decision pattern | Key fields |
619| :-------------------------------------------------------------------------------------------------------------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |701| :---------------------------------------------------------------------------------------------------------------------------------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
620| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange, PreCompact | Top-level `decision` | `decision: "block"`, `reason` |702| 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 |703| TeammateIdle, TaskCreated, TaskCompleted | Exit code or `continue: false` | Exit code 2 blocks the action with stderr feedback. JSON `{"continue": false, "stopReason": "..."}` also stops the teammate entirely, matching `Stop` hook behavior |
622| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask/defer), `permissionDecisionReason` |704| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask/defer), `permissionDecisionReason` |
623| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |705| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |
631 713
632<Tabs>714<Tabs>
633 <Tab title="Top-level decision">715 <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:716 Used by `UserPromptSubmit`, `UserPromptExpansion`, `PostToolUse`, `PostToolUseFailure`, `PostToolBatch`, `Stop`, `SubagentStop`, `ConfigChange`, and `PreCompact`. The only value is `"block"`. To allow the action to proceed, omit `decision` from your JSON, or exit 0 without any JSON at all:
635 717
636 ```json theme={null}718 ```json theme={null}
637 {719 {
684 766
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.767Runs when Claude Code starts a new session or resumes an existing session. Useful for loading development context like existing issues or recent changes to your codebase, or setting up environment variables. For static context that does not require a script, use [CLAUDE.md](/en/memory) instead.
686 768
687SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` hooks are supported.769SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` and `type: "mcp_tool"` hooks are supported.
688 770
689The matcher value corresponds to how the session was initiated:771The matcher value corresponds to how the session was initiated:
690 772
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:797Any text your hook script prints to stdout is added as context for Claude. In addition to the [JSON output fields](#json-output) available to all hooks, you can return these event-specific fields:
716 798
717| Field | Description |799| Field | Description |
718| :------------------ | :------------------------------------------------------------------------ |800| :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
719| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |801| `additionalContext` | String added to Claude's context at the start of the conversation, before the first prompt. See [Add context for Claude](#add-context-for-claude) for how the text is delivered and what to put in it |
720 802
721```json theme={null}803```json theme={null}
722{804{
723 "hookSpecificOutput": {805 "hookSpecificOutput": {
724 "hookEventName": "SessionStart",806 "hookEventName": "SessionStart",
725 "additionalContext": "My additional context here"807 "additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2"
726 }808 }
727}809}
728```810```
729 811
812Since plain stdout already reaches Claude for this event, a hook that only loads context can print to stdout directly without building JSON. Use the JSON form when you need to combine context with other fields such as `suppressOutput`.
813
730#### Persist environment variables814#### Persist environment variables
731 815
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.816SessionStart hooks have access to the `CLAUDE_ENV_FILE` environment variable, which provides a file path where you can persist environment variables for subsequent Bash commands.
767Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.851Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.
768 852
769<Note>853<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.854 `CLAUDE_ENV_FILE` is available for SessionStart, [Setup](#setup), [CwdChanged](#cwdchanged), and [FileChanged](#filechanged) hooks. Other hook types do not have access to this variable.
771</Note>855</Note>
772 856
857### Setup
858
859Fires only when you launch Claude Code with `--init-only`, or with `--init` or `--maintenance` in print mode (`-p`). It does not fire on normal startup. Use it for one-time dependency installation or scheduled cleanup that you trigger explicitly from CI or scripts, separate from normal session startup. For per-session initialization, use [SessionStart](#sessionstart) instead.
860
861The matcher value corresponds to the CLI flag that triggered the hook:
862
863| Matcher | When it fires |
864| :------------ | :----------------------------------------- |
865| `init` | `claude --init-only` or `claude -p --init` |
866| `maintenance` | `claude -p --maintenance` |
867
868`--init-only` runs Setup hooks and `SessionStart` hooks with the `startup` matcher, then exits without starting a conversation. `--init` and `--maintenance` fire Setup hooks only when combined with `-p` (print mode); in an interactive session those two flags do not currently fire Setup hooks.
869
870Because Setup does not fire on every launch, a plugin that needs a dependency installed cannot rely on Setup alone. The practical pattern is to check for the dependency on first use and install on miss, for example a hook or skill that tests for `${CLAUDE_PLUGIN_DATA}/node_modules` and runs `npm install` if absent. See the [persistent data directory](/en/plugins-reference#persistent-data-directory) for where to store installed dependencies.
871
872#### Setup input
873
874In addition to the [common input fields](#common-input-fields), Setup hooks receive a `trigger` field set to either `"init"` or `"maintenance"`:
875
876```json theme={null}
877{
878 "session_id": "abc123",
879 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
880 "cwd": "/Users/...",
881 "hook_event_name": "Setup",
882 "trigger": "init"
883}
884```
885
886#### Setup decision control
887
888Setup hooks cannot block. On exit code 2, stderr is shown to the user; on any other non-zero exit code, stderr appears only when you launch with `--verbose`. In both cases execution continues. To pass information into Claude's context, return `additionalContext` in JSON output; plain stdout is written to the debug log only. In addition to the [JSON output fields](#json-output) available to all hooks, you can return these event-specific fields:
889
890| Field | Description |
891| :------------------ | :------------------------------------------------------------------------ |
892| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |
893
894```json theme={null}
895{
896 "hookSpecificOutput": {
897 "hookEventName": "Setup",
898 "additionalContext": "Dependencies installed: node_modules, .venv"
899 }
900}
901```
902
903Setup hooks have access to `CLAUDE_ENV_FILE`. Variables written to that file persist into subsequent Bash commands for the session, just as in [SessionStart hooks](#persist-environment-variables). Only `type: "command"` and `type: "mcp_tool"` hooks are supported.
904
773### InstructionsLoaded905### InstructionsLoaded
774 906
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.907Fires when a `CLAUDE.md` or `.claude/rules/*.md` file is loaded into context. This event fires at session start for eagerly-loaded files and again later when files are lazily loaded, for example when Claude accesses a subdirectory that contains a nested `CLAUDE.md` or when conditional rules with `paths:` frontmatter match. The hook does not support blocking or decision control. It runs asynchronously for observability purposes.
840To block a prompt, return a JSON object with `decision` set to `"block"`:972To block a prompt, return a JSON object with `decision` set to `"block"`:
841 973
842| Field | Description |974| Field | Description |
843| :------------------ | :----------------------------------------------------------------------------------------------------------------- |975| :------------------ | :--------------------------------------------------------------------------------------------------------------------- |
844| `decision` | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |976| `decision` | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |
845| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |977| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |
846| `additionalContext` | String added to Claude's context |978| `additionalContext` | String added to Claude's context alongside the submitted prompt. See [Add context for Claude](#add-context-for-claude) |
847| `sessionTitle` | Sets the session title, same effect as `/rename`. Use to name sessions automatically based on the prompt content |979| `sessionTitle` | Sets the session title, same effect as `/rename`. Use to name sessions automatically based on the prompt content |
848 980
849```json theme={null}981```json theme={null}
863 block prompts or want more structured control.995 block prompts or want more structured control.
864</Note>996</Note>
865 997
998### UserPromptExpansion
999
1000Runs when a user-typed slash command expands into a prompt before reaching Claude. Use this to block specific commands from direct invocation, inject context for a particular skill, or log which commands users invoke. For example, a hook matching `deploy` can block `/deploy` unless an approval file is present, or a hook matching a review skill can append the team's review checklist as `additionalContext`.
1001
1002This event covers the path `PreToolUse` does not: a `PreToolUse` hook matching the `Skill` tool fires only when Claude calls the tool, but typing `/skillname` directly bypasses `PreToolUse`. `UserPromptExpansion` fires on that direct path.
1003
1004Matches on `command_name`. Leave the matcher empty to fire on every prompt-type slash command.
1005
1006#### UserPromptExpansion input
1007
1008In addition to the [common input fields](#common-input-fields), UserPromptExpansion hooks receive `expansion_type`, `command_name`, `command_args`, `command_source`, and the original `prompt` string. The `expansion_type` field is `slash_command` for skill and custom commands, or `mcp_prompt` for MCP server prompts.
1009
1010```json theme={null}
1011{
1012 "session_id": "abc123",
1013 "transcript_path": "/Users/.../00893aaf.jsonl",
1014 "cwd": "/Users/...",
1015 "permission_mode": "default",
1016 "hook_event_name": "UserPromptExpansion",
1017 "expansion_type": "slash_command",
1018 "command_name": "example-skill",
1019 "command_args": "arg1 arg2",
1020 "command_source": "plugin",
1021 "prompt": "/example-skill arg1 arg2"
1022}
1023```
1024
1025#### UserPromptExpansion decision control
1026
1027`UserPromptExpansion` hooks can block the expansion or add context. All [JSON output fields](#json-output) are available.
1028
1029| Field | Description |
1030| :------------------ | :-------------------------------------------------------------------------------------------------------------------- |
1031| `decision` | `"block"` prevents the slash command from expanding. Omit to allow it to proceed |
1032| `reason` | Shown to the user when `decision` is `"block"` |
1033| `additionalContext` | String added to Claude's context alongside the expanded prompt. See [Add context for Claude](#add-context-for-claude) |
1034
1035```json theme={null}
1036{
1037 "decision": "block",
1038 "reason": "This slash command is not available",
1039 "hookSpecificOutput": {
1040 "hookEventName": "UserPromptExpansion",
1041 "additionalContext": "Additional context for this expansion"
1042 }
1043}
1044```
1045
866### PreToolUse1046### PreToolUse
867 1047
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).1048Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name: `Bash`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Agent`, `WebFetch`, `WebSearch`, `AskUserQuestion`, `ExitPlanMode`, and any [MCP tool names](#match-mcp-tools).
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 |1164| `permissionDecision` | `"allow"` skips the permission prompt. `"deny"` prevents the tool call. `"ask"` prompts the user to confirm. `"defer"` exits gracefully so the tool can be resumed later. [Deny and ask rules](/en/permissions#manage-permissions) are still evaluated regardless of what the hook returns |
985| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude. For `"defer"`, ignored |1165| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude. For `"defer"`, ignored |
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 |1166| `updatedInput` | Modifies the tool's input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones. Combine with `"allow"` to auto-approve, or `"ask"` to show the modified input to the user. For `"defer"`, ignored |
987| `additionalContext` | String added to Claude's context before the tool executes. For `"defer"`, ignored |1167| `additionalContext` | String added to Claude's context alongside the tool result. Ignored when `permissionDecision` is `"defer"`. See [Add context for Claude](#add-context-for-claude) |
988 1168
989When multiple PreToolUse hooks return different decisions, precedence is `deny` > `defer` > `ask` > `allow`.1169When multiple PreToolUse hooks return different decisions, precedence is `deny` > `defer` > `ask` > `allow`.
990 1170
1042}1222}
1043```1223```
1044 1224
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.1225There is no timeout or retry limit. The session remains on disk until you resume it, subject to the [`cleanupPeriodDays`](/en/settings#available-settings) retention sweep that deletes session files after 30 days by default. If the answer is not ready when you resume, the hook can return `"defer"` again and the process exits the same way. The calling process controls when to break the loop by eventually returning `"allow"` or `"deny"` from the hook.
1046 1226
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.1227`"defer"` only works when Claude makes a single tool call in the turn. If Claude makes several tool calls at once, `"defer"` is ignored with a warning and the tool proceeds through the normal permission flow. The constraint exists because resume can only re-run one tool: there is no way to defer one call from a batch without leaving the others unresolved.
1048 1228
1166 "filePath": "/path/to/file.txt",1346 "filePath": "/path/to/file.txt",
1167 "success": true1347 "success": true
1168 },1348 },
1169 "tool_use_id": "toolu_01ABC123..."1349 "tool_use_id": "toolu_01ABC123...",
1350 "duration_ms": 12
1170}1351}
1171```1352```
1172 1353
1354| Field | Description |
1355| :------------ | :------------------------------------------------------------------------------------------------------------ |
1356| `duration_ms` | Optional. Tool execution time in milliseconds. Excludes time spent in permission prompts and PreToolUse hooks |
1357
1173#### PostToolUse decision control1358#### PostToolUse decision control
1174 1359
1175`PostToolUse` hooks can provide feedback to Claude after tool execution. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:1360`PostToolUse` hooks can provide feedback to Claude after tool execution. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:
1176 1361
1177| Field | Description |1362| Field | Description |
1178| :--------------------- | :----------------------------------------------------------------------------------------- |1363| :--------------------- | :--------------------------------------------------------------------------------------------------------------------------- |
1179| `decision` | `"block"` prompts Claude with the `reason`. Omit to allow the action to proceed |1364| `decision` | `"block"` prompts Claude with the `reason`. Omit to allow the action to proceed |
1180| `reason` | Explanation shown to Claude when `decision` is `"block"` |1365| `reason` | Explanation shown to Claude when `decision` is `"block"` |
1181| `additionalContext` | Additional context for Claude to consider |1366| `additionalContext` | String added to Claude's context alongside the tool result. See [Add context for Claude](#add-context-for-claude) |
1182| `updatedMCPToolOutput` | For [MCP tools](#match-mcp-tools) only: replaces the tool's output with the provided value |1367| `updatedToolOutput` | Replaces the tool's output with the provided value before it is sent to Claude. The value must match the tool's output shape |
1368| `updatedMCPToolOutput` | Replaces the output for [MCP tools](#match-mcp-tools) only. Prefer `updatedToolOutput`, which works for all tools |
1369
1370The example below replaces the output of a `Bash` call. The replacement value matches the `Bash` tool's output shape:
1183 1371
1184```json theme={null}1372```json theme={null}
1185{1373{
1186 "decision": "block",
1187 "reason": "Explanation for decision",
1188 "hookSpecificOutput": {1374 "hookSpecificOutput": {
1189 "hookEventName": "PostToolUse",1375 "hookEventName": "PostToolUse",
1190 "additionalContext": "Additional information for Claude"1376 "additionalContext": "Additional information for Claude",
1377 "updatedToolOutput": {
1378 "stdout": "[redacted]",
1379 "stderr": "",
1380 "interrupted": false,
1381 "isImage": false
1382 }
1191 }1383 }
1192}1384}
1193```1385```
1194 1386
1387<Warning>
1388 `updatedToolOutput` only changes what Claude sees. The tool has already run by the time the hook fires, so any files written, commands executed, or network requests sent have already taken effect. Telemetry such as OpenTelemetry tool spans and analytics events also captures the original output before the hook runs. To prevent or modify a tool call before it runs, use a [PreToolUse](#pretooluse) hook instead.
1389
1390 The replacement value must match the tool's output shape. Built-in tools return structured objects rather than plain strings. For example, `Bash` returns an object with `stdout`, `stderr`, `interrupted`, and `isImage` fields. For built-in tools, a value that does not match the tool's output schema is ignored and the original output is used. MCP tool output is passed through without schema validation. Stripping error details that Claude needs can cause it to proceed on a false assumption.
1391</Warning>
1392
1195### PostToolUseFailure1393### PostToolUseFailure
1196 1394
1197Runs when a tool execution fails. This event fires for tool calls that throw errors or return failure results. Use this to log failures, send alerts, or provide corrective feedback to Claude.1395Runs when a tool execution fails. This event fires for tool calls that throw errors or return failure results. Use this to log failures, send alerts, or provide corrective feedback to Claude.
1216 },1414 },
1217 "tool_use_id": "toolu_01ABC123...",1415 "tool_use_id": "toolu_01ABC123...",
1218 "error": "Command exited with non-zero status code 1",1416 "error": "Command exited with non-zero status code 1",
1219 "is_interrupt": false1417 "is_interrupt": false,
1418 "duration_ms": 4187
1220}1419}
1221```1420```
1222 1421
1223| Field | Description |1422| Field | Description |
1224| :------------- | :------------------------------------------------------------------------------ |1423| :------------- | :------------------------------------------------------------------------------------------------------------ |
1225| `error` | String describing what went wrong |1424| `error` | String describing what went wrong |
1226| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |1425| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |
1426| `duration_ms` | Optional. Tool execution time in milliseconds. Excludes time spent in permission prompts and PreToolUse hooks |
1227 1427
1228#### PostToolUseFailure decision control1428#### PostToolUseFailure decision control
1229 1429
1230`PostToolUseFailure` hooks can provide context to Claude after a tool failure. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:1430`PostToolUseFailure` hooks can provide context to Claude after a tool failure. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:
1231 1431
1232| Field | Description |1432| Field | Description |
1233| :------------------ | :------------------------------------------------------------ |1433| :------------------ | :---------------------------------------------------------------------------------------------------------- |
1234| `additionalContext` | Additional context for Claude to consider alongside the error |1434| `additionalContext` | String added to Claude's context alongside the error. See [Add context for Claude](#add-context-for-claude) |
1235 1435
1236```json theme={null}1436```json theme={null}
1237{1437{
1242}1442}
1243```1443```
1244 1444
1445### PostToolBatch
1446
1447Runs once after every tool call in a batch has resolved, before Claude Code sends the next request to the model. `PostToolUse` fires once per tool, which means it fires concurrently when Claude makes parallel tool calls. `PostToolBatch` fires exactly once with the full batch, so it is the right place to inject context that depends on the set of tools that ran rather than on any single tool. There is no matcher for this event.
1448
1449#### PostToolBatch input
1450
1451In addition to the [common input fields](#common-input-fields), PostToolBatch hooks receive `tool_calls`, an array describing every tool call in the batch:
1452
1453```json theme={null}
1454{
1455 "session_id": "abc123",
1456 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1457 "cwd": "/Users/...",
1458 "permission_mode": "default",
1459 "hook_event_name": "PostToolBatch",
1460 "tool_calls": [
1461 {
1462 "tool_name": "Read",
1463 "tool_input": {"file_path": "/.../ledger/accounts.py"},
1464 "tool_use_id": "toolu_01...",
1465 "tool_response": " 1\tfrom __future__ import annotations\n 2\t..."
1466 },
1467 {
1468 "tool_name": "Read",
1469 "tool_input": {"file_path": "/.../ledger/transactions.py"},
1470 "tool_use_id": "toolu_02...",
1471 "tool_response": " 1\tfrom __future__ import annotations\n 2\t..."
1472 }
1473 ]
1474}
1475```
1476
1477`tool_response` contains the same content the model receives in the corresponding `tool_result` block. The value is a serialized string or content-block array, exactly as the tool emitted it. For `Read`, that means line-number-prefixed text rather than raw file contents. Responses can be large, so parse only the fields you need.
1478
1479<Note>
1480 The `tool_response` shape differs from `PostToolUse`'s. `PostToolUse` passes the tool's structured `Output` object, such as `{filePath: "...", success: true}` for `Write`; `PostToolBatch` passes the serialized `tool_result` content the model sees.
1481</Note>
1482
1483#### PostToolBatch decision control
1484
1485`PostToolBatch` hooks can inject context for Claude. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:
1486
1487| Field | Description |
1488| :------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1489| `additionalContext` | Context string injected once before the next model call. See [Add context for Claude](#add-context-for-claude) for delivery details, what to put in it, and how resumed sessions handle past values |
1490
1491```json theme={null}
1492{
1493 "hookSpecificOutput": {
1494 "hookEventName": "PostToolBatch",
1495 "additionalContext": "These files are part of the ledger module. Run pytest before marking the task complete."
1496 }
1497}
1498```
1499
1500Returning `decision: "block"` or `continue: false` stops the agentic loop before the next model call.
1501
1245### PermissionDenied1502### PermissionDenied
1246 1503
1247Runs when the [auto mode](/en/permission-modes#eliminate-prompts-with-auto-mode) classifier denies a tool call. This hook only fires in auto mode: it does not run when you manually deny a permission dialog, when a `PreToolUse` hook blocks a call, or when a `deny` rule matches. Use it to log classifier denials, adjust configuration, or tell the model it may retry the tool call.1504Runs when the [auto mode](/en/permission-modes#eliminate-prompts-with-auto-mode) classifier denies a tool call. This hook only fires in auto mode: it does not run when you manually deny a permission dialog, when a `PreToolUse` hook blocks a call, or when a `deny` rule matches. Use it to log classifier denials, adjust configuration, or tell the model it may retry the tool call.
1290 1547
1291### Notification1548### Notification
1292 1549
1293Runs when Claude Code sends notifications. Matches on notification type: `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`. Omit the matcher to run hooks for all notification types.1550Runs when Claude Code sends notifications. Matches on notification type: `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`, `elicitation_complete`, `elicitation_response`. Omit the matcher to run hooks for all notification types.
1294 1551
1295Use separate matchers to run different handlers depending on the notification type. This configuration triggers a permission-specific alert script when Claude needs permission approval and a different notification when Claude has been idle:1552Use separate matchers to run different handlers depending on the notification type. This configuration triggers a permission-specific alert script when Claude needs permission approval and a different notification when Claude has been idle:
1296 1553
1337}1594}
1338```1595```
1339 1596
1340Notification hooks cannot block or modify notifications. In addition to the [JSON output fields](#json-output) available to all hooks, you can return `additionalContext` to add context to the conversation:1597Notification hooks cannot block or modify notifications. They are intended for side effects such as forwarding the notification to an external service. The [common JSON output fields](#json-output) such as `systemMessage` apply.
1341
1342| Field | Description |
1343| :------------------ | :------------------------------- |
1344| `additionalContext` | String added to Claude's context |
1345 1598
1346### SubagentStart1599### SubagentStart
1347 1600
1348Runs when a Claude Code subagent is spawned via the Agent tool. Supports matchers to filter by agent type name (built-in agents like `Bash`, `Explore`, `Plan`, or custom agent names from `.claude/agents/`).1601Runs when a Claude Code subagent is spawned via the Agent tool. Supports matchers to filter by agent type name (built-in agents like `general-purpose`, `Explore`, `Plan`, or custom agent names from `.claude/agents/`).
1349 1602
1350#### SubagentStart input1603#### SubagentStart input
1351 1604
1352In addition to the [common input fields](#common-input-fields), SubagentStart hooks receive `agent_id` with the unique identifier for the subagent and `agent_type` with the agent name (built-in agents like `"Bash"`, `"Explore"`, `"Plan"`, or custom agent names).1605In addition to the [common input fields](#common-input-fields), SubagentStart hooks receive `agent_id` with the unique identifier for the subagent and `agent_type` with the agent name (built-in agents like `"general-purpose"`, `"Explore"`, `"Plan"`, or custom agent names).
1353 1606
1354```json theme={null}1607```json theme={null}
1355{1608{
1365SubagentStart hooks cannot block subagent creation, but they can inject context into the subagent. In addition to the [JSON output fields](#json-output) available to all hooks, you can return:1618SubagentStart hooks cannot block subagent creation, but they can inject context into the subagent. In addition to the [JSON output fields](#json-output) available to all hooks, you can return:
1366 1619
1367| Field | Description |1620| Field | Description |
1368| :------------------ | :------------------------------------- |1621| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------ |
1369| `additionalContext` | String added to the subagent's context |1622| `additionalContext` | String added to the subagent's context at the start of its conversation, before its first prompt. See [Add context for Claude](#add-context-for-claude) |
1370 1623
1371```json theme={null}1624```json theme={null}
1372{1625{
1561 1814
1562| Field | Description |1815| Field | Description |
1563| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |1816| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1564| `error` | Error type: `rate_limit`, `authentication_failed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, or `unknown` |1817| `error` | Error type: `rate_limit`, `authentication_failed`, `oauth_org_not_allowed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, or `unknown` |
1565| `error_details` | Additional details about the error, when available |1818| `error_details` | Additional details about the error, when available |
1566| `last_assistant_message` | The rendered error text shown in the conversation. Unlike `Stop` and `SubagentStop`, where this field holds Claude's conversational output, for `StopFailure` it contains the API error string itself, such as `"API Error: Rate limit reached"` |1819| `last_assistant_message` | The rendered error text shown in the conversation. Unlike `Stop` and `SubagentStop`, where this field holds Claude's conversational output, for `StopFailure` it contains the API error string itself, such as `"API Error: Rate limit reached"` |
1567 1820
1698 1951
1699Runs when the working directory changes during a session, for example when Claude executes a `cd` command. Use this to react to directory changes: reload environment variables, activate project-specific toolchains, or run setup scripts automatically. Pairs with [FileChanged](#filechanged) for tools like [direnv](https://direnv.net/) that manage per-directory environment.1952Runs when the working directory changes during a session, for example when Claude executes a `cd` command. Use this to react to directory changes: reload environment variables, activate project-specific toolchains, or run setup scripts automatically. Pairs with [FileChanged](#filechanged) for tools like [direnv](https://direnv.net/) that manage per-directory environment.
1700 1953
1701CwdChanged hooks have access to `CLAUDE_ENV_FILE`. Variables written to that file persist into subsequent Bash commands for the session, just as in [SessionStart hooks](#persist-environment-variables). Only `type: "command"` hooks are supported.1954CwdChanged hooks have access to `CLAUDE_ENV_FILE`. Variables written to that file persist into subsequent Bash commands for the session, just as in [SessionStart hooks](#persist-environment-variables).
1702 1955
1703CwdChanged does not support matchers and fires on every directory change.1956CwdChanged does not support matchers and fires on every directory change.
1704 1957
1736* **Build the watch list**: the value is split on `|` and each segment is registered as a literal filename in the working directory, so `".envrc|.env"` watches exactly those two files. Regex patterns are not useful here: a value like `^\.env` would watch a file literally named `^\.env`.1989* **Build the watch list**: the value is split on `|` and each segment is registered as a literal filename in the working directory, so `".envrc|.env"` watches exactly those two files. Regex patterns are not useful here: a value like `^\.env` would watch a file literally named `^\.env`.
1737* **Filter which hooks run**: when a watched file changes, the same value filters which hook groups run using the standard [matcher rules](#matcher-patterns) against the changed file's basename.1990* **Filter which hooks run**: when a watched file changes, the same value filters which hook groups run using the standard [matcher rules](#matcher-patterns) against the changed file's basename.
1738 1991
1739FileChanged hooks have access to `CLAUDE_ENV_FILE`. Variables written to that file persist into subsequent Bash commands for the session, just as in [SessionStart hooks](#persist-environment-variables). Only `type: "command"` hooks are supported.1992FileChanged hooks have access to `CLAUDE_ENV_FILE`. Variables written to that file persist into subsequent Bash commands for the session, just as in [SessionStart hooks](#persist-environment-variables).
1740 1993
1741#### FileChanged input1994#### FileChanged input
1742 1995
2073 2326
2074## Prompt-based hooks2327## Prompt-based hooks
2075 2328
2076In addition to command and HTTP hooks, Claude Code supports prompt-based hooks (`type: "prompt"`) that use an LLM to evaluate whether to allow or block an action, and agent hooks (`type: "agent"`) that spawn an agentic verifier with tool access. Not all events support every hook type.2329In addition to command, HTTP, and MCP tool hooks, Claude Code supports prompt-based hooks (`type: "prompt"`) that use an LLM to evaluate whether to allow or block an action, and agent hooks (`type: "agent"`) that spawn an agentic verifier with tool access. Not all events support every hook type.
2077 2330
2078Events that support all four hook types (`command`, `http`, `prompt`, and `agent`):2331Events that support all five hook types (`command`, `http`, `mcp_tool`, `prompt`, and `agent`):
2079 2332
2080* `PermissionRequest`2333* `PermissionRequest`
2334* `PostToolBatch`
2081* `PostToolUse`2335* `PostToolUse`
2082* `PostToolUseFailure`2336* `PostToolUseFailure`
2083* `PreToolUse`2337* `PreToolUse`
2085* `SubagentStop`2339* `SubagentStop`
2086* `TaskCompleted`2340* `TaskCompleted`
2087* `TaskCreated`2341* `TaskCreated`
2342* `UserPromptExpansion`
2088* `UserPromptSubmit`2343* `UserPromptSubmit`
2089 2344
2090Events that support `command` and `http` hooks but not `prompt` or `agent`:2345Events that support `command`, `http`, and `mcp_tool` hooks but not `prompt` or `agent`:
2091 2346
2092* `ConfigChange`2347* `ConfigChange`
2093* `CwdChanged`2348* `CwdChanged`
2106* `WorktreeCreate`2361* `WorktreeCreate`
2107* `WorktreeRemove`2362* `WorktreeRemove`
2108 2363
2109`SessionStart` supports only `command` hooks.2364`SessionStart` and `Setup` support `command` and `mcp_tool` hooks. They do not support `http`, `prompt`, or `agent` hooks.
2110 2365
2111### How prompt-based hooks work2366### How prompt-based hooks work
2112 2367
2186 2441
2187## Agent-based hooks2442## Agent-based hooks
2188 2443
2444<Warning>
2445 Agent hooks are experimental. Behavior and configuration may change in future releases. For production workflows, prefer [command hooks](#command-hook-fields).
2446</Warning>
2447
2189Agent-based hooks (`type: "agent"`) are like prompt-based hooks but with multi-turn tool access. Instead of a single LLM call, an agent hook spawns a subagent that can read files, search code, and inspect the codebase to verify conditions. Agent hooks support the same events as prompt-based hooks.2448Agent-based hooks (`type: "agent"`) are like prompt-based hooks but with multi-turn tool access. Instead of a single LLM call, an agent hook spawns a subagent that can read files, search code, and inspect the codebase to verify conditions. Agent hooks support the same events as prompt-based hooks.
2190 2449
2191### How agent hooks work2450### How agent hooks work
2387 2646
2388For more granular hook matching details, set `CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose` to see additional log lines such as hook matcher counts and query matching.2647For more granular hook matching details, set `CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose` to see additional log lines such as hook matcher counts and query matching.
2389 2648
2390For troubleshooting common issues like hooks not firing, infinite Stop hook loops, or configuration errors, see [Limitations and troubleshooting](/en/hooks-guide#limitations-and-troubleshooting) in the guide.2649For troubleshooting common issues like hooks not firing, infinite Stop hook loops, or configuration errors, see [Limitations and troubleshooting](/en/hooks-guide#limitations-and-troubleshooting) in the guide. For a broader diagnostic walkthrough covering `/context`, `/doctor`, and settings precedence, see [Debug your config](/en/debug-your-config).