18 18
19<div style={{maxWidth: "500px", margin: "0 auto"}}>19<div style={{maxWidth: "500px", margin: "0 auto"}}>
20 <Frame>20 <Frame>
21 <img src="https://mintcdn.com/claude-code/UMJp-WgTWngzO609/images/hooks-lifecycle.svg?fit=max&auto=format&n=UMJp-WgTWngzO609&q=85&s=3f4de67df216c87dc313943b32c15f62" alt="Hook lifecycle diagram showing SessionStart, then a per-turn loop containing UserPromptSubmit, the nested agentic loop (PreToolUse, PermissionRequest, PostToolUse, SubagentStart/Stop, TaskCreated, TaskCompleted), and Stop or StopFailure, followed by TeammateIdle, PreCompact, PostCompact, and SessionEnd, with Elicitation and ElicitationResult nested inside MCP tool execution, PermissionDenied as a side branch from PermissionRequest for auto-mode denials, and WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, and FileChanged as standalone async events" width="520" height="1155" data-path="images/hooks-lifecycle.svg" />21 <img src="https://mintcdn.com/claude-code/ZIW26Z9pnpsXLhbS/images/hooks-lifecycle.svg?fit=max&auto=format&n=ZIW26Z9pnpsXLhbS&q=85&s=ee23691324deb6501df09bfdae560b64" alt="Hook lifecycle diagram showing optional Setup feeding into SessionStart, then a per-turn loop containing UserPromptSubmit, UserPromptExpansion for slash commands, the nested agentic loop (PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, PostToolBatch, SubagentStart/Stop, TaskCreated, TaskCompleted), and Stop or StopFailure, followed by TeammateIdle, PreCompact, PostCompact, and SessionEnd, with Elicitation and ElicitationResult nested inside MCP tool execution, PermissionDenied as a side branch from PermissionRequest for auto-mode denials, and WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, and FileChanged as standalone async events" width="520" height="1228" data-path="images/hooks-lifecycle.svg" />
22 </Frame>22 </Frame>
23</div>23</div>
24 24
25The table below summarizes when each event fires. The [Hook events](#hook-events) section documents the full input schema and decision control options for each one.25The table below summarizes when each event fires. The [Hook events](#hook-events) section documents the full input schema and decision control options for each one.
26 26
27| Event | When it fires |27| Event | When it fires |
28| :------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- |28| :-------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
29| `SessionStart` | When a session begins or resumes |29| `SessionStart` | When a session begins or resumes |
30| `Setup` | When you start Claude Code with `--init-only`, or with `--init` or `--maintenance` in `-p` mode. For one-time preparation in CI or scripts |
30| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |31| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |
32| `UserPromptExpansion` | When a user-typed command expands into a prompt, before it reaches Claude. Can block the expansion |
31| `PreToolUse` | Before a tool call executes. Can block it |33| `PreToolUse` | Before a tool call executes. Can block it |
32| `PermissionRequest` | When a permission dialog appears |34| `PermissionRequest` | When a permission dialog appears |
33| `PermissionDenied` | When a tool call is denied by the auto mode classifier. Return `{retry: true}` to tell the model it may retry the denied tool call |35| `PermissionDenied` | When a tool call is denied by the auto mode classifier. Return `{retry: true}` to tell the model it may retry the denied tool call |
34| `PostToolUse` | After a tool call succeeds |36| `PostToolUse` | After a tool call succeeds |
35| `PostToolUseFailure` | After a tool call fails |37| `PostToolUseFailure` | After a tool call fails |
38| `PostToolBatch` | After a full batch of parallel tool calls resolves, before the next model call |
36| `Notification` | When Claude Code sends a notification |39| `Notification` | When Claude Code sends a notification |
37| `SubagentStart` | When a subagent is spawned |40| `SubagentStart` | When a subagent is spawned |
38| `SubagentStop` | When a subagent finishes |41| `SubagentStop` | When a subagent finishes |
55 58
56### How a hook resolves59### How a hook resolves
57 60
58To see how these pieces fit together, consider this `PreToolUse` hook that blocks destructive shell commands. The `matcher` narrows to Bash tool calls and the `if` condition narrows further to commands starting with `rm`, so `block-rm.sh` only spawns when both filters match:61To see how these pieces fit together, consider this `PreToolUse` hook that blocks destructive shell commands. The `matcher` narrows to Bash tool calls and the `if` condition narrows further to Bash subcommands matching `rm *`, so `block-rm.sh` only spawns when both filters match:
59 62
60```json theme={null}63```json theme={null}
61{64{
116 </Step>119 </Step>
117 120
118 <Step title="If condition checks">121 <Step title="If condition checks">
119 The `if` condition `"Bash(rm *)"` matches because the command starts with `rm`, so this handler spawns. If the command had been `npm test`, the `if` check would fail and `block-rm.sh` would never run, avoiding the process spawn overhead. The `if` field is optional; without it, every handler in the matched group runs.122 The `if` condition `"Bash(rm *)"` matches because `rm -rf /tmp/build` is a subcommand matching `rm *`, so this handler spawns. If the command had been `npm test`, the `if` check would fail and `block-rm.sh` would never run, avoiding the process spawn overhead. The `if` field is optional; without it, every handler in the matched group runs.
120 </Step>123 </Step>
121 124
122 <Step title="Hook handler runs">125 <Step title="Hook handler runs">
153See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.156See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.
154 157
155<Note>158<Note>
156 This page uses specific terms for each level: **hook event** for the lifecycle point, **matcher group** for the filter, and **hook handler** for the shell command, HTTP endpoint, prompt, or agent that runs. "Hook" on its own refers to the general feature.159 This page uses specific terms for each level: **hook event** for the lifecycle point, **matcher group** for the filter, and **hook handler** for the shell command, HTTP endpoint, MCP tool, prompt, or agent that runs. "Hook" on its own refers to the general feature.
157</Note>160</Note>
158 161
159### Hook locations162### Hook locations
186Each event type matches on a different field:189Each event type matches on a different field:
187 190
188| Event | What the matcher filters | Example matcher values |191| Event | What the matcher filters | Example matcher values |
189| :------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ |192| :------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
190| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |193| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |
191| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |194| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |
195| `Setup` | which CLI flag triggered setup | `init`, `maintenance` |
192| `SessionEnd` | why the session ended | `clear`, `resume`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |196| `SessionEnd` | why the session ended | `clear`, `resume`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |
193| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |197| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`, `elicitation_complete`, `elicitation_response` |
194| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |198| `SubagentStart` | agent type | `general-purpose`, `Explore`, `Plan`, or custom agent names |
195| `PreCompact`, `PostCompact` | what triggered compaction | `manual`, `auto` |199| `PreCompact`, `PostCompact` | what triggered compaction | `manual`, `auto` |
196| `SubagentStop` | agent type | same values as `SubagentStart` |200| `SubagentStop` | agent type | same values as `SubagentStart` |
197| `ConfigChange` | configuration source | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` |201| `ConfigChange` | configuration source | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` |
198| `CwdChanged` | no matcher support | always fires on every directory change |202| `CwdChanged` | no matcher support | always fires on every directory change |
199| `FileChanged` | literal filenames to watch (see [FileChanged](#filechanged)) | `.envrc\|.env` |203| `FileChanged` | literal filenames to watch (see [FileChanged](#filechanged)) | `.envrc\|.env` |
200| `StopFailure` | error type | `rate_limit`, `authentication_failed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, `unknown` |204| `StopFailure` | error type | `rate_limit`, `authentication_failed`, `oauth_org_not_allowed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, `unknown` |
201| `InstructionsLoaded` | load reason | `session_start`, `nested_traversal`, `path_glob_match`, `include`, `compact` |205| `InstructionsLoaded` | load reason | `session_start`, `nested_traversal`, `path_glob_match`, `include`, `compact` |
206| `UserPromptExpansion` | command name | your skill or command names |
202| `Elicitation` | MCP server name | your configured MCP server names |207| `Elicitation` | MCP server name | your configured MCP server names |
203| `ElicitationResult` | MCP server name | same values as `Elicitation` |208| `ElicitationResult` | MCP server name | same values as `Elicitation` |
204| `UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove` | no matcher support | always fires on every occurrence |209| `UserPromptSubmit`, `PostToolBatch`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove` | no matcher support | always fires on every occurrence |
205 210
206The matcher runs against a field from the [JSON input](#hook-input-and-output) that Claude Code sends to your hook on stdin. For tool events, that field is `tool_name`. Each [hook event](#hook-events) section lists the full set of matcher values and the input schema for that event.211The matcher runs against a field from the [JSON input](#hook-input-and-output) that Claude Code sends to your hook on stdin. For tool events, that field is `tool_name`. Each [hook event](#hook-events) section lists the full set of matcher values and the input schema for that event.
207 212
225}230}
226```231```
227 232
228`UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove`, and `CwdChanged` don't support matchers and always fire on every occurrence. If you add a `matcher` field to these events, it is silently ignored.233`UserPromptSubmit`, `PostToolBatch`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove`, and `CwdChanged` don't support matchers and always fire on every occurrence. If you add a `matcher` field to these events, it is silently ignored.
229 234
230For tool events, you can filter more narrowly by setting the [`if` field](#common-fields) on individual hook handlers. `if` uses [permission rule syntax](/en/permissions) to match against the tool name and arguments together, so `"Bash(git *)"` runs only for `git` commands and `"Edit(*.ts)"` runs only for TypeScript files.235For tool events, you can filter more narrowly by setting the [`if` field](#common-fields) on individual hook handlers. `if` uses [permission rule syntax](/en/permissions) to match against the tool name and arguments together, so `"Bash(git *)"` runs when any subcommand of the Bash input matches `git *` and `"Edit(*.ts)"` runs only for TypeScript files.
231 236
232#### Match MCP tools237#### Match MCP tools
233 238
275 280
276### Hook handler fields281### Hook handler fields
277 282
278Each object in the inner `hooks` array is a hook handler: the shell command, HTTP endpoint, LLM prompt, or agent that runs when the matcher matches. There are four types:283Each object in the inner `hooks` array is a hook handler: the shell command, HTTP endpoint, MCP tool, LLM prompt, or agent that runs when the matcher matches. There are five types:
279 284
280* **[Command hooks](#command-hook-fields)** (`type: "command"`): run a shell command. Your script receives the event's [JSON input](#hook-input-and-output) on stdin and communicates results back through exit codes and stdout.285* **[Command hooks](#command-hook-fields)** (`type: "command"`): run a shell command. Your script receives the event's [JSON input](#hook-input-and-output) on stdin and communicates results back through exit codes and stdout.
281* **[HTTP hooks](#http-hook-fields)** (`type: "http"`): send the event's JSON input as an HTTP POST request to a URL. The endpoint communicates results back through the response body using the same [JSON output format](#json-output) as command hooks.286* **[HTTP hooks](#http-hook-fields)** (`type: "http"`): send the event's JSON input as an HTTP POST request to a URL. The endpoint communicates results back through the response body using the same [JSON output format](#json-output) as command hooks.
287* **[MCP tool hooks](#mcp-tool-hook-fields)** (`type: "mcp_tool"`): call a tool on an already-connected [MCP server](/en/mcp). The tool's text output is treated like command-hook stdout.
282* **[Prompt hooks](#prompt-and-agent-hook-fields)** (`type: "prompt"`): send a prompt to a Claude model for single-turn evaluation. The model returns a yes/no decision as JSON. See [Prompt-based hooks](#prompt-based-hooks).288* **[Prompt hooks](#prompt-and-agent-hook-fields)** (`type: "prompt"`): send a prompt to a Claude model for single-turn evaluation. The model returns a yes/no decision as JSON. See [Prompt-based hooks](#prompt-based-hooks).
283* **[Agent hooks](#prompt-and-agent-hook-fields)** (`type: "agent"`): spawn a subagent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. See [Agent-based hooks](#agent-based-hooks).289* **[Agent hooks](#prompt-and-agent-hook-fields)** (`type: "agent"`): spawn a subagent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. Agent hooks are experimental and may change. See [Agent-based hooks](#agent-based-hooks).
284 290
285#### Common fields291#### Common fields
286 292
287These fields apply to all hook types:293These fields apply to all hook types:
288 294
289| Field | Required | Description |295| Field | Required | Description |
290| :-------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |296| :-------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
291| `type` | yes | `"command"`, `"http"`, `"prompt"`, or `"agent"` |297| `type` | yes | `"command"`, `"http"`, `"mcp_tool"`, `"prompt"`, or `"agent"` |
292| `if` | no | Permission rule syntax to filter when this hook runs, such as `"Bash(git *)"` or `"Edit(*.ts)"`. The hook only spawns if the tool call matches the pattern. Only evaluated on tool events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, and `PermissionDenied`. On other events, a hook with `if` set never runs. Uses the same syntax as [permission rules](/en/permissions) |298| `if` | no | Permission rule syntax to filter when this hook runs, such as `"Bash(git *)"` or `"Edit(*.ts)"`. The hook only spawns if the tool call matches the pattern, or if a Bash command is too complex to parse. Only evaluated on tool events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, and `PermissionDenied`. On other events, a hook with `if` set never runs. Uses the same syntax as [permission rules](/en/permissions) |
293| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |299| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |
294| `statusMessage` | no | Custom spinner message displayed while the hook runs |300| `statusMessage` | no | Custom spinner message displayed while the hook runs |
295| `once` | no | If `true`, runs only once per session then is removed. Skills only, not agents. See [Hooks in skills and agents](#hooks-in-skills-and-agents) |301| `once` | no | If `true`, runs once per session then is removed. Only honored for hooks declared in [skill frontmatter](#hooks-in-skills-and-agents); ignored in settings files and agent frontmatter |
302
303The `if` field holds exactly one permission rule. There is no `&&`, `||`, or list syntax for combining rules; to apply multiple conditions, define a separate hook handler for each. For Bash, the rule is matched against each subcommand of the tool input after leading `VAR=value` assignments are stripped, so `if: "Bash(git push *)"` matches both `FOO=bar git push` and `npm test && git push`. The hook runs if any subcommand matches, and always runs when the command is too complex to parse.
296 304
297#### Command hook fields305#### Command hook fields
298 306
344}352}
345```353```
346 354
355#### MCP tool hook fields
356
357In addition to the [common fields](#common-fields), MCP tool hooks accept these fields:
358
359| Field | Required | Description |
360| :------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
361| `server` | yes | Name of a configured MCP server. The server must already be connected; the hook never triggers an OAuth or connection flow |
362| `tool` | yes | Name of the tool to call on that server |
363| `input` | no | Arguments passed to the tool. String values support `${path}` substitution from the hook's [JSON input](#hook-input-and-output), such as `"${tool_input.file_path}"` |
364
365The tool's text content is treated like command-hook stdout: if it parses as valid [JSON output](#json-output) it is processed as a decision, otherwise it is shown as plain text. If the named server is not connected, or the tool returns `isError: true`, the hook produces a non-blocking error and execution continues.
366
367MCP tool hooks are available on every hook event once Claude Code has connected to your MCP servers. `SessionStart` and `Setup` typically fire before servers finish connecting, so hooks on those events should expect the "not connected" error on first run.
368
369This example calls the `security_scan` tool on the `my_server` MCP server after each `Write` or `Edit`, passing the edited file's path:
370
371```json theme={null}
372{
373 "hooks": {
374 "PostToolUse": [
375 {
376 "matcher": "Write|Edit",
377 "hooks": [
378 {
379 "type": "mcp_tool",
380 "server": "my_server",
381 "tool": "security_scan",
382 "input": { "file_path": "${tool_input.file_path}" }
383 }
384 ]
385 }
386 ]
387 }
388}
389```
390
347#### Prompt and agent hook fields391#### Prompt and agent hook fields
348 392
349In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:393In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:
444 488
445Type `/hooks` in Claude Code to open a read-only browser for your configured hooks. The menu shows every hook event with a count of configured hooks, lets you drill into matchers, and shows the full details of each hook handler. Use it to verify configuration, check which settings file a hook came from, or inspect a hook's command, prompt, or URL.489Type `/hooks` in Claude Code to open a read-only browser for your configured hooks. The menu shows every hook event with a count of configured hooks, lets you drill into matchers, and shows the full details of each hook handler. Use it to verify configuration, check which settings file a hook came from, or inspect a hook's command, prompt, or URL.
446 490
447The menu displays all four hook types: `command`, `prompt`, `agent`, and `http`. Each hook is labeled with a `[type]` prefix and a source indicating where it was defined:491The menu displays all five hook types: `command`, `prompt`, `agent`, `http`, and `mcp_tool`. Each hook is labeled with a `[type]` prefix and a source indicating where it was defined:
448 492
449* `User`: from `~/.claude/settings.json`493* `User`: from `~/.claude/settings.json`
450* `Project`: from `.claude/settings.json`494* `Project`: from `.claude/settings.json`
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
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:1271`PermissionRequest` hooks can allow or deny permission requests. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return a `decision` object with these event-specific fields:
1092 1272
1093| Field | Description |1273| Field | Description |
1094| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |1274| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1095| `behavior` | `"allow"` grants the permission, `"deny"` denies it |1275| `behavior` | `"allow"` grants the permission, `"deny"` denies it. [Deny and ask rules](/en/permissions#manage-permissions) are still evaluated, so a hook returning `"allow"` does not override a matching deny rule |
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 |1276| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones. The modified input is re-evaluated against deny and ask rules |
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 |1277| `updatedPermissions` | For `"allow"` only: array of [permission update entries](#permission-update-entries) to apply, such as adding an allow rule or changing the session permission mode |
1098| `message` | For `"deny"` only: tells Claude why the permission was denied |1278| `message` | For `"deny"` only: tells Claude why the permission was denied |
1099| `interrupt` | For `"deny"` only: if `true`, stops Claude |1279| `interrupt` | For `"deny"` only: if `true`, stops Claude |
1125| `addDirectories` | `directories`, `destination` | Adds working directories. `directories` is an array of path strings |1305| `addDirectories` | `directories`, `destination` | Adds working directories. `directories` is an array of path strings |
1126| `removeDirectories` | `directories`, `destination` | Removes working directories |1306| `removeDirectories` | `directories`, `destination` | Removes working directories |
1127 1307
1308<Note>
1309 `setMode` with `bypassPermissions` only takes effect if the session was launched with bypass mode already available: `--dangerously-skip-permissions`, `--permission-mode bypassPermissions`, `--allow-dangerously-skip-permissions`, or `permissions.defaultMode: "bypassPermissions"` in settings, and the mode is not disabled by [`permissions.disableBypassPermissionsMode`](/en/permissions#managed-settings). Otherwise the update is a no-op. `bypassPermissions` is never persisted as `defaultMode` regardless of `destination`.
1310</Note>
1311
1128The `destination` field on every entry determines whether the change stays in memory or persists to a settings file.1312The `destination` field on every entry determines whether the change stays in memory or persists to a settings file.
1129 1313
1130| `destination` | Writes to |1314| `destination` | Writes to |
1162 "filePath": "/path/to/file.txt",1346 "filePath": "/path/to/file.txt",
1163 "success": true1347 "success": true
1164 },1348 },
1165 "tool_use_id": "toolu_01ABC123..."1349 "tool_use_id": "toolu_01ABC123...",
1350 "duration_ms": 12
1166}1351}
1167```1352```
1168 1353
1354| Field | Description |
1355| :------------ | :------------------------------------------------------------------------------------------------------------ |
1356| `duration_ms` | Optional. Tool execution time in milliseconds. Excludes time spent in permission prompts and PreToolUse hooks |
1357
1169#### PostToolUse decision control1358#### PostToolUse decision control
1170 1359
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: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:
1172 1361
1173| Field | Description |1362| Field | Description |
1174| :--------------------- | :----------------------------------------------------------------------------------------- |1363| :--------------------- | :--------------------------------------------------------------------------------------------------------------------------- |
1175| `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 |
1176| `reason` | Explanation shown to Claude when `decision` is `"block"` |1365| `reason` | Explanation shown to Claude when `decision` is `"block"` |
1177| `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) |
1178| `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:
1179 1371
1180```json theme={null}1372```json theme={null}
1181{1373{
1182 "decision": "block",
1183 "reason": "Explanation for decision",
1184 "hookSpecificOutput": {1374 "hookSpecificOutput": {
1185 "hookEventName": "PostToolUse",1375 "hookEventName": "PostToolUse",
1186 "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 }
1187 }1383 }
1188}1384}
1189```1385```
1190 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
1191### PostToolUseFailure1393### PostToolUseFailure
1192 1394
1193Runs when a tool execution fails. This event fires for tool calls that throw errors or return failure results. Use this to log failures, send alerts, or provide corrective feedback to Claude.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.
1212 },1414 },
1213 "tool_use_id": "toolu_01ABC123...",1415 "tool_use_id": "toolu_01ABC123...",
1214 "error": "Command exited with non-zero status code 1",1416 "error": "Command exited with non-zero status code 1",
1215 "is_interrupt": false1417 "is_interrupt": false,
1418 "duration_ms": 4187
1216}1419}
1217```1420```
1218 1421
1219| Field | Description |1422| Field | Description |
1220| :------------- | :------------------------------------------------------------------------------ |1423| :------------- | :------------------------------------------------------------------------------------------------------------ |
1221| `error` | String describing what went wrong |1424| `error` | String describing what went wrong |
1222| `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 |
1223 1427
1224#### PostToolUseFailure decision control1428#### PostToolUseFailure decision control
1225 1429
1226`PostToolUseFailure` hooks can provide context to Claude after a tool failure. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields: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:
1227 1431
1228| Field | Description |1432| Field | Description |
1229| :------------------ | :------------------------------------------------------------ |1433| :------------------ | :---------------------------------------------------------------------------------------------------------- |
1230| `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) |
1231 1435
1232```json theme={null}1436```json theme={null}
1233{1437{
1238}1442}
1239```1443```
1240 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
1241### PermissionDenied1502### PermissionDenied
1242 1503
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.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.
1286 1547
1287### Notification1548### Notification
1288 1549
1289Runs when Claude Code sends notifications. Matches on notification type: `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`. Omit the matcher to run hooks for all notification types.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.
1290 1551
1291Use separate matchers to run different handlers depending on the notification type. This configuration triggers a permission-specific alert script when Claude needs permission approval and a different notification when Claude has been idle: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:
1292 1553
1333}1594}
1334```1595```
1335 1596
1336Notification hooks cannot block or modify notifications. In addition to the [JSON output fields](#json-output) available to all hooks, you can return `additionalContext` to add context to the conversation: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.
1337
1338| Field | Description |
1339| :------------------ | :------------------------------- |
1340| `additionalContext` | String added to Claude's context |
1341 1598
1342### SubagentStart1599### SubagentStart
1343 1600
1344Runs when a Claude Code subagent is spawned via the Agent tool. Supports matchers to filter by agent type name (built-in agents like `Bash`, `Explore`, `Plan`, or custom agent names from `.claude/agents/`).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/`).
1345 1602
1346#### SubagentStart input1603#### SubagentStart input
1347 1604
1348In addition to the [common input fields](#common-input-fields), SubagentStart hooks receive `agent_id` with the unique identifier for the subagent and `agent_type` with the agent name (built-in agents like `"Bash"`, `"Explore"`, `"Plan"`, or custom agent names).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).
1349 1606
1350```json theme={null}1607```json theme={null}
1351{1608{
1361SubagentStart hooks cannot block subagent creation, but they can inject context into the subagent. In addition to the [JSON output fields](#json-output) available to all hooks, you can return: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:
1362 1619
1363| Field | Description |1620| Field | Description |
1364| :------------------ | :------------------------------------- |1621| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------ |
1365| `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) |
1366 1623
1367```json theme={null}1624```json theme={null}
1368{1625{
1557 1814
1558| Field | Description |1815| Field | Description |
1559| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |1816| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1560| `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` |
1561| `error_details` | Additional details about the error, when available |1818| `error_details` | Additional details about the error, when available |
1562| `last_assistant_message` | The rendered error text shown in the conversation. Unlike `Stop` and `SubagentStop`, where this field holds Claude's conversational output, for `StopFailure` it contains the API error string itself, such as `"API Error: Rate limit reached"` |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"` |
1563 1820
1694 1951
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.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.
1696 1953
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.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).
1698 1955
1699CwdChanged does not support matchers and fires on every directory change.1956CwdChanged does not support matchers and fires on every directory change.
1700 1957
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`.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`.
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.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.
1734 1991
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.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).
1736 1993
1737#### FileChanged input1994#### FileChanged input
1738 1995
1768 2025
1769When you run `claude --worktree` or a [subagent uses `isolation: "worktree"`](/en/sub-agents#choose-the-subagent-scope), Claude Code creates an isolated working copy using `git worktree`. If you configure a WorktreeCreate hook, it replaces the default git behavior, letting you use a different version control system like SVN, Perforce, or Mercurial.2026When you run `claude --worktree` or a [subagent uses `isolation: "worktree"`](/en/sub-agents#choose-the-subagent-scope), Claude Code creates an isolated working copy using `git worktree`. If you configure a WorktreeCreate hook, it replaces the default git behavior, letting you use a different version control system like SVN, Perforce, or Mercurial.
1770 2027
1771Because the hook replaces the default behavior entirely, [`.worktreeinclude`](/en/common-workflows#copy-gitignored-files-to-worktrees) is not processed. If you need to copy local configuration files like `.env` into the new worktree, do it inside your hook script.2028Because the hook replaces the default behavior entirely, [`.worktreeinclude`](/en/worktrees#copy-gitignored-files-into-worktrees) is not processed. If you need to copy local configuration files like `.env` into the new worktree, do it inside your hook script.
1772 2029
1773The hook must return the absolute path to the created worktree directory. Claude Code uses this path as the working directory for the isolated session. Command hooks print it on stdout; HTTP hooks return it via `hookSpecificOutput.worktreePath`.2030The hook must return the absolute path to the created worktree directory. Claude Code uses this path as the working directory for the isolated session. Command hooks print it on stdout; HTTP hooks return it via `hookSpecificOutput.worktreePath`.
1774 2031
2069 2326
2070## Prompt-based hooks2327## Prompt-based hooks
2071 2328
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.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.
2073 2330
2074Events 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`):
2075 2332
2076* `PermissionRequest`2333* `PermissionRequest`
2334* `PostToolBatch`
2077* `PostToolUse`2335* `PostToolUse`
2078* `PostToolUseFailure`2336* `PostToolUseFailure`
2079* `PreToolUse`2337* `PreToolUse`
2081* `SubagentStop`2339* `SubagentStop`
2082* `TaskCompleted`2340* `TaskCompleted`
2083* `TaskCreated`2341* `TaskCreated`
2342* `UserPromptExpansion`
2084* `UserPromptSubmit`2343* `UserPromptSubmit`
2085 2344
2086Events 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`:
2087 2346
2088* `ConfigChange`2347* `ConfigChange`
2089* `CwdChanged`2348* `CwdChanged`
2102* `WorktreeCreate`2361* `WorktreeCreate`
2103* `WorktreeRemove`2362* `WorktreeRemove`
2104 2363
2105`SessionStart` supports only `command` hooks.2364`SessionStart` and `Setup` support `command` and `mcp_tool` hooks. They do not support `http`, `prompt`, or `agent` hooks.
2106 2365
2107### How prompt-based hooks work2366### How prompt-based hooks work
2108 2367
2154```2413```
2155 2414
2156| Field | Description |2415| Field | Description |
2157| :------- | :--------------------------------------------------------- |2416| :------- | :------------------------------------------------------- |
2158| `ok` | `true` allows the action, `false` prevents it |2417| `ok` | `true` allows the action, `false` blocks it |
2159| `reason` | Required when `ok` is `false`. Explanation shown to Claude |2418| `reason` | Required when `ok` is `false`. Explanation for the block |
2419
2420For `Stop` and `SubagentStop`, an `ok: false` reason is fed back to Claude as its next instruction and the turn continues. For all other supported events, the turn ends and the reason appears in the chat as a warning line; Claude does not see it. This is equivalent to returning `"continue": false` from a command hook. If you need different blocking semantics on those events, use a [command hook](#command-hook-fields) with the per-event fields described in [Decision control](#decision-control).
2160 2421
2161### Example: Multi-criteria Stop hook2422### Example: Multi-criteria Stop hook
2162 2423
2182 2443
2183## Agent-based hooks2444## Agent-based hooks
2184 2445
2446<Warning>
2447 Agent hooks are experimental. Behavior and configuration may change in future releases. For production workflows, prefer [command hooks](#command-hook-fields).
2448</Warning>
2449
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.2450Agent-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 2451
2187### How agent hooks work2452### How agent hooks work
2383 2648
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.2649For 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 2650
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.2651For 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).