4 4
5# Hooks reference5# Hooks reference
6 6
7> Reference for Claude Code hook events, configuration schema, JSON input/output formats, exit codes, async hooks, prompt hooks, and MCP tool hooks.7> Reference for Claude Code hook events, configuration schema, JSON input/output formats, exit codes, async hooks, HTTP hooks, prompt hooks, and MCP tool hooks.
8 8
9<Tip>9<Tip>
10 For a quickstart guide with examples, see [Automate workflows with hooks](/en/hooks-guide).10 For a quickstart guide with examples, see [Automate workflows with hooks](/en/hooks-guide).
11</Tip>11</Tip>
12 12
13Hooks are user-defined shell commands or LLM prompts that execute automatically at specific points in Claude Code's lifecycle. Use this reference to look up event schemas, configuration options, JSON input/output formats, and advanced features like async hooks and MCP tool hooks. If you're setting up hooks for the first time, start with the [guide](/en/hooks-guide) instead.13Hooks are user-defined shell commands, HTTP endpoints, or LLM prompts that execute automatically at specific points in Claude Code's lifecycle. Use this reference to look up event schemas, configuration options, JSON input/output formats, and advanced features like async hooks, HTTP hooks, and MCP tool hooks. If you're setting up hooks for the first time, start with the [guide](/en/hooks-guide) instead.
14 14
15## Hook lifecycle15## Hook lifecycle
16 16
17Hooks fire at specific points during a Claude Code session. When an event fires and a matcher matches, Claude Code passes JSON context about the event to your hook handler. For command hooks, this arrives on stdin. Your handler can then inspect the input, take action, and optionally return a decision. Some events fire once per session, while others fire repeatedly inside the agentic loop:17Hooks fire at specific points during a Claude Code session. When an event fires and a matcher matches, Claude Code passes JSON context about the event to your hook handler. For command hooks, input arrives on stdin. For HTTP hooks, it arrives as the POST request body. Your handler can then inspect the input, take action, and optionally return a decision. Some events fire once per session, while others fire repeatedly inside the agentic loop:
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/rsuu-ovdPNos9Dnn/images/hooks-lifecycle.svg?fit=max&auto=format&n=rsuu-ovdPNos9Dnn&q=85&s=ce5f1225339bbccdfbb52e99205db912" alt="Hook lifecycle diagram showing the sequence of hooks from SessionStart through the agentic loop to SessionEnd, with WorktreeCreate and WorktreeRemove as standalone setup and teardown events" data-og-width="520" width="520" data-og-height="1020" height="1020" data-path="images/hooks-lifecycle.svg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/claude-code/rsuu-ovdPNos9Dnn/images/hooks-lifecycle.svg?w=280&fit=max&auto=format&n=rsuu-ovdPNos9Dnn&q=85&s=7c7143c65492c1beb6bc66f5d206ba15 280w, https://mintcdn.com/claude-code/rsuu-ovdPNos9Dnn/images/hooks-lifecycle.svg?w=560&fit=max&auto=format&n=rsuu-ovdPNos9Dnn&q=85&s=dafaebf8f789f94edbf6bd66853c69df 560w, https://mintcdn.com/claude-code/rsuu-ovdPNos9Dnn/images/hooks-lifecycle.svg?w=840&fit=max&auto=format&n=rsuu-ovdPNos9Dnn&q=85&s=2caa51d2d95596f1f80b92e3f5f534fa 840w, https://mintcdn.com/claude-code/rsuu-ovdPNos9Dnn/images/hooks-lifecycle.svg?w=1100&fit=max&auto=format&n=rsuu-ovdPNos9Dnn&q=85&s=614def559f34f9b0c1dec93739d96b64 1100w, https://mintcdn.com/claude-code/rsuu-ovdPNos9Dnn/images/hooks-lifecycle.svg?w=1650&fit=max&auto=format&n=rsuu-ovdPNos9Dnn&q=85&s=ca45b85fdd8b2da81c69d12c453230cb 1650w, https://mintcdn.com/claude-code/rsuu-ovdPNos9Dnn/images/hooks-lifecycle.svg?w=2500&fit=max&auto=format&n=rsuu-ovdPNos9Dnn&q=85&s=7fd92d6b9713493f59962c9f295c9d2f 2500w" />21 <img src="https://mintcdn.com/claude-code/WLZtXlltXc8aIoIM/images/hooks-lifecycle.svg?fit=max&auto=format&n=WLZtXlltXc8aIoIM&q=85&s=6a0bf67eeb570a96e36b564721fa2a93" alt="Hook lifecycle diagram showing the sequence of hooks from SessionStart through the agentic loop (PreToolUse, PermissionRequest, PostToolUse, SubagentStart/Stop, TaskCreated, TaskCompleted) to Stop or StopFailure, TeammateIdle, PreCompact, PostCompact, and SessionEnd, with Elicitation and ElicitationResult nested inside MCP tool execution, PermissionDenied as a side branch from PermissionRequest for auto-mode denials, and WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, and FileChanged as standalone async events" width="520" height="1155" data-path="images/hooks-lifecycle.svg" />
22 </Frame>22 </Frame>
23</div>23</div>
24 24
25The table below summarizes when each event fires. The [Hook events](#hook-events) section documents the full input schema and decision control options for each one.25The table below summarizes when each event fires. The [Hook events](#hook-events) section documents the full input schema and decision control options for each one.
26 26
27| Event | When it fires |27| Event | When it fires |
28| :------------------- | :---------------------------------------------------------------------------------------------------------- |28| :------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
29| `SessionStart` | When a session begins or resumes |29| `SessionStart` | When a session begins or resumes |
30| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |30| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |
31| `PreToolUse` | Before a tool call executes. Can block it |31| `PreToolUse` | Before a tool call executes. Can block it |
32| `PermissionRequest` | When a permission dialog appears |32| `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 |
33| `PostToolUse` | After a tool call succeeds |34| `PostToolUse` | After a tool call succeeds |
34| `PostToolUseFailure` | After a tool call fails |35| `PostToolUseFailure` | After a tool call fails |
35| `Notification` | When Claude Code sends a notification |36| `Notification` | When Claude Code sends a notification |
36| `SubagentStart` | When a subagent is spawned |37| `SubagentStart` | When a subagent is spawned |
37| `SubagentStop` | When a subagent finishes |38| `SubagentStop` | When a subagent finishes |
39| `TaskCreated` | When a task is being created via `TaskCreate` |
40| `TaskCompleted` | When a task is being marked as completed |
38| `Stop` | When Claude finishes responding |41| `Stop` | When Claude finishes responding |
42| `StopFailure` | When the turn ends due to an API error. Output and exit code are ignored |
39| `TeammateIdle` | When an [agent team](/en/agent-teams) teammate is about to go idle |43| `TeammateIdle` | When an [agent team](/en/agent-teams) teammate is about to go idle |
40| `TaskCompleted` | When a task is being marked as completed |44| `InstructionsLoaded` | When a CLAUDE.md or `.claude/rules/*.md` file is loaded into context. Fires at session start and when files are lazily loaded during a session |
41| `ConfigChange` | When a configuration file changes during a session |45| `ConfigChange` | When a configuration file changes during a session |
46| `CwdChanged` | When the working directory changes, for example when Claude executes a `cd` command. Useful for reactive environment management with tools like direnv |
47| `FileChanged` | When a watched file changes on disk. The `matcher` field specifies which filenames to watch |
42| `WorktreeCreate` | When a worktree is being created via `--worktree` or `isolation: "worktree"`. Replaces default git behavior |48| `WorktreeCreate` | When a worktree is being created via `--worktree` or `isolation: "worktree"`. Replaces default git behavior |
43| `WorktreeRemove` | When a worktree is being removed, either at session exit or when a subagent finishes |49| `WorktreeRemove` | When a worktree is being removed, either at session exit or when a subagent finishes |
44| `PreCompact` | Before context compaction |50| `PreCompact` | Before context compaction |
51| `PostCompact` | After context compaction completes |
52| `Elicitation` | When an MCP server requests user input during a tool call |
53| `ElicitationResult` | After a user responds to an MCP elicitation, before the response is sent back to the server |
45| `SessionEnd` | When a session terminates |54| `SessionEnd` | When a session terminates |
46 55
47### How a hook resolves56### How a hook resolves
48 57
49To see how these pieces fit together, consider this `PreToolUse` hook that blocks destructive shell commands. The hook runs `block-rm.sh` before every Bash tool call: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:
50 59
51```json theme={null}60```json theme={null}
52{61{
57 "hooks": [66 "hooks": [
58 {67 {
59 "type": "command",68 "type": "command",
60 "command": ".claude/hooks/block-rm.sh"69 "if": "Bash(rm *)",
70 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh"
61 }71 }
62 ]72 ]
63 }73 }
89Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:99Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:
90 100
91<Frame>101<Frame>
92 <img src="https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=5bb890134390ecd0581477cf41ef730b" alt="Hook resolution flow: PreToolUse event fires, matcher checks for Bash match, hook handler runs, result returns to Claude Code" data-og-width="780" width="780" data-og-height="290" height="290" data-path="images/hook-resolution.svg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=280&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=5dcaecd24c260b8a90365d74e2c1fcda 280w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=560&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=c03d91c279f01d92e58ddd70fdbe66f2 560w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=840&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=1be57a4819cbb949a5ea9d08a05c9ecd 840w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=1100&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=0e9dd1807dc7a5c56011d0889b0d5208 1100w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=1650&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=69496ac02e70fabfece087ba31a1dcfc 1650w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=2500&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=a012346cb46a33b86580348802055267 2500w" />102 <img src="https://mintcdn.com/claude-code/-tYw1BD_DEqfyyOZ/images/hook-resolution.svg?fit=max&auto=format&n=-tYw1BD_DEqfyyOZ&q=85&s=c73ebc1eeda2037570427d7af1e0a891" alt="Hook resolution flow: PreToolUse event fires, matcher checks for Bash match, if condition checks for Bash(rm *) match, hook handler runs, result returns to Claude Code" width="930" height="290" data-path="images/hook-resolution.svg" />
93</Frame>103</Frame>
94 104
95<Steps>105<Steps>
102 </Step>112 </Step>
103 113
104 <Step title="Matcher checks">114 <Step title="Matcher checks">
105 The matcher `"Bash"` matches the tool name, so `block-rm.sh` runs. If you omit the matcher or use `"*"`, the hook runs on every occurrence of the event. Hooks only skip when a matcher is defined and doesn't match.115 The matcher `"Bash"` matches the tool name, so this hook group activates. If you omit the matcher or use `"*"`, the group activates on every occurrence of the event.
116 </Step>
117
118 <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.
106 </Step>120 </Step>
107 121
108 <Step title="Hook handler runs">122 <Step title="Hook handler runs">
109 The script extracts `"rm -rf /tmp/build"` from the input and finds `rm -rf`, so it prints a decision to stdout:123 The script inspects the full command and finds `rm -rf`, so it prints a decision to stdout:
110 124
111 ```json theme={null}125 ```json theme={null}
112 {126 {
118 }132 }
119 ```133 ```
120 134
121 If the command had been safe (like `npm test`), the script would hit `exit 0` instead, which tells Claude Code to allow the tool call with no further action.135 If the command had been a safer `rm` variant like `rm file.txt`, the script would hit `exit 0` instead, which tells Claude Code to allow the tool call with no further action.
122 </Step>136 </Step>
123 137
124 <Step title="Claude Code acts on the result">138 <Step title="Claude Code acts on the result">
139See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.153See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.
140 154
141<Note>155<Note>
142 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, prompt, or agent that runs. "Hook" on its own refers to the general feature.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.
143</Note>157</Note>
144 158
145### Hook locations159### Hook locations
162The `matcher` field is a regex string that filters when hooks fire. Use `"*"`, `""`, or omit `matcher` entirely to match all occurrences. Each event type matches on a different field:176The `matcher` field is a regex string that filters when hooks fire. Use `"*"`, `""`, or omit `matcher` entirely to match all occurrences. Each event type matches on a different field:
163 177
164| Event | What the matcher filters | Example matcher values |178| Event | What the matcher filters | Example matcher values |
165| :---------------------------------------------------------------------------------------------- | :------------------------ | :--------------------------------------------------------------------------------- |179| :------------------------------------------------------------------------------------------------------------- | :-------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ |
166| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |180| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |
167| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |181| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |
168| `SessionEnd` | why the session ended | `clear`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |182| `SessionEnd` | why the session ended | `clear`, `resume`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |
169| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |183| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |
170| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |184| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |
171| `PreCompact` | what triggered compaction | `manual`, `auto` |185| `PreCompact`, `PostCompact` | what triggered compaction | `manual`, `auto` |
172| `SubagentStop` | agent type | same values as `SubagentStart` |186| `SubagentStop` | agent type | same values as `SubagentStart` |
173| `ConfigChange` | configuration source | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` |187| `ConfigChange` | configuration source | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` |
174| `UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove` | no matcher support | always fires on every occurrence |188| `CwdChanged` | no matcher support | always fires on every directory change |
189| `FileChanged` | filename (basename of the changed file) | `.envrc`, `.env`, any filename you want to watch |
190| `StopFailure` | error type | `rate_limit`, `authentication_failed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, `unknown` |
191| `InstructionsLoaded` | load reason | `session_start`, `nested_traversal`, `path_glob_match`, `include`, `compact` |
192| `Elicitation` | MCP server name | your configured MCP server names |
193| `ElicitationResult` | MCP server name | same values as `Elicitation` |
194| `UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove` | no matcher support | always fires on every occurrence |
175 195
176The matcher is a regex, so `Edit|Write` matches either tool and `Notebook.*` matches any tool starting with Notebook. The 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.196The matcher is a regex, so `Edit|Write` matches either tool and `Notebook.*` matches any tool starting with Notebook. The 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.
177 197
195}215}
196```216```
197 217
198`UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCompleted`, `WorktreeCreate`, and `WorktreeRemove` don't support matchers and always fire on every occurrence. If you add a `matcher` field to these events, it is silently ignored.218`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.
219
220For 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.
199 221
200#### Match MCP tools222#### Match MCP tools
201 223
202[MCP](/en/mcp) server tools appear as regular tools in tool events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`), so you can match them the same way you match any other tool name.224[MCP](/en/mcp) server tools appear as regular tools in tool events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied`), so you can match them the same way you match any other tool name.
203 225
204MCP tools follow the naming pattern `mcp__<server>__<tool>`, for example:226MCP tools follow the naming pattern `mcp__<server>__<tool>`, for example:
205 227
243 265
244### Hook handler fields266### Hook handler fields
245 267
246Each object in the inner `hooks` array is a hook handler: the shell command, LLM prompt, or agent that runs when the matcher matches. There are three types:268Each 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:
247 269
248* **[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.270* **[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.
271* **[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.
249* **[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).272* **[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).
250* **[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).273* **[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).
251 274
254These fields apply to all hook types:277These fields apply to all hook types:
255 278
256| Field | Required | Description |279| Field | Required | Description |
257| :-------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |280| :-------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
258| `type` | yes | `"command"`, `"prompt"`, or `"agent"` |281| `type` | yes | `"command"`, `"http"`, `"prompt"`, or `"agent"` |
282| `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) |
259| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |283| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |
260| `statusMessage` | no | Custom spinner message displayed while the hook runs |284| `statusMessage` | no | Custom spinner message displayed while the hook runs |
261| `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) |285| `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) |
265In addition to the [common fields](#common-fields), command hooks accept these fields:289In addition to the [common fields](#common-fields), command hooks accept these fields:
266 290
267| Field | Required | Description |291| Field | Required | Description |
268| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------ |292| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
269| `command` | yes | Shell command to execute |293| `command` | yes | Shell command to execute |
270| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |294| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |
295| `shell` | no | Shell to use for this hook. Accepts `"bash"` (default) or `"powershell"`. Setting `"powershell"` runs the command via PowerShell on Windows. Does not require `CLAUDE_CODE_USE_POWERSHELL_TOOL` since hooks spawn PowerShell directly |
296
297#### HTTP hook fields
298
299In addition to the [common fields](#common-fields), HTTP hooks accept these fields:
300
301| Field | Required | Description |
302| :--------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
303| `url` | yes | URL to send the POST request to |
304| `headers` | no | Additional HTTP headers as key-value pairs. Values support environment variable interpolation using `$VAR_NAME` or `${VAR_NAME}` syntax. Only variables listed in `allowedEnvVars` are resolved |
305| `allowedEnvVars` | no | List of environment variable names that may be interpolated into header values. References to unlisted variables are replaced with empty strings. Required for any env var interpolation to work |
306
307Claude Code sends the hook's [JSON input](#hook-input-and-output) as the POST request body with `Content-Type: application/json`. The response body uses the same [JSON output format](#json-output) as command hooks.
308
309Error handling differs from command hooks: non-2xx responses, connection failures, and timeouts all produce non-blocking errors that allow execution to continue. To block a tool call or deny a permission, return a 2xx response with a JSON body containing `decision: "block"` or a `hookSpecificOutput` with `permissionDecision: "deny"`.
310
311This example sends `PreToolUse` events to a local validation service, authenticating with a token from the `MY_TOKEN` environment variable:
312
313```json theme={null}
314{
315 "hooks": {
316 "PreToolUse": [
317 {
318 "matcher": "Bash",
319 "hooks": [
320 {
321 "type": "http",
322 "url": "http://localhost:8080/hooks/pre-tool-use",
323 "timeout": 30,
324 "headers": {
325 "Authorization": "Bearer $MY_TOKEN"
326 },
327 "allowedEnvVars": ["MY_TOKEN"]
328 }
329 ]
330 }
331 ]
332 }
333}
334```
271 335
272#### Prompt and agent hook fields336#### Prompt and agent hook fields
273 337
278| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |342| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
279| `model` | no | Model to use for evaluation. Defaults to a fast model |343| `model` | no | Model to use for evaluation. Defaults to a fast model |
280 344
281All matching hooks run in parallel, and identical handlers are deduplicated automatically. Handlers run in the current directory with Claude Code's environment. The `$CLAUDE_CODE_REMOTE` environment variable is set to `"true"` in remote web environments and not set in the local CLI.345All matching hooks run in parallel, and identical handlers are deduplicated automatically. Command hooks are deduplicated by command string, and HTTP hooks are deduplicated by URL. Handlers run in the current directory with Claude Code's environment. The `$CLAUDE_CODE_REMOTE` environment variable is set to `"true"` in remote web environments and not set in the local CLI.
282 346
283### Reference scripts by path347### Reference scripts by path
284 348
285Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:349Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:
286 350
287* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.351* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.
288* `${CLAUDE_PLUGIN_ROOT}`: the plugin's root directory, for scripts bundled with a [plugin](/en/plugins).352* `${CLAUDE_PLUGIN_ROOT}`: the plugin's installation directory, for scripts bundled with a [plugin](/en/plugins). Changes on each plugin update.
353* `${CLAUDE_PLUGIN_DATA}`: the plugin's [persistent data directory](/en/plugins-reference#persistent-data-directory), for dependencies and state that should survive plugin updates.
289 354
290<Tabs>355<Tabs>
291 <Tab title="Project scripts">356 <Tab title="Project scripts">
366 431
367### The `/hooks` menu432### The `/hooks` menu
368 433
369Type `/hooks` in Claude Code to open the interactive hooks manager, where you can view, add, and delete hooks without editing settings files directly. For a step-by-step walkthrough, see [Set up your first hook](/en/hooks-guide#set-up-your-first-hook) in the guide.434Type `/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.
370 435
371Each hook in the menu is labeled with a bracket prefix indicating its source:436The 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:
372 437
373* `[User]`: from `~/.claude/settings.json`438* `User`: from `~/.claude/settings.json`
374* `[Project]`: from `.claude/settings.json`439* `Project`: from `.claude/settings.json`
375* `[Local]`: from `.claude/settings.local.json`440* `Local`: from `.claude/settings.local.json`
376* `[Plugin]`: from a plugin's `hooks/hooks.json`, read-only441* `Plugin`: from a plugin's `hooks/hooks.json`
442* `Session`: registered in memory for the current session
443* `Built-in`: registered internally by Claude Code
444
445Selecting a hook opens a detail view showing its event, matcher, type, source file, and the full command, prompt, or URL. The menu is read-only: to add, modify, or remove hooks, edit the settings JSON directly or ask Claude to make the change.
377 446
378### Disable or remove hooks447### Disable or remove hooks
379 448
380To remove a hook, delete its entry from the settings JSON file, or use the `/hooks` menu and select the hook to delete it.449To remove a hook, delete its entry from the settings JSON file.
381 450
382To temporarily disable all hooks without removing them, set `"disableAllHooks": true` in your settings file or use the toggle in the `/hooks` menu. There is no way to disable an individual hook while keeping it in the configuration.451To temporarily disable all hooks without removing them, set `"disableAllHooks": true` in your settings file. There is no way to disable an individual hook while keeping it in the configuration.
383 452
384The `disableAllHooks` setting respects the managed settings hierarchy. If an administrator has configured hooks through managed policy settings, `disableAllHooks` set in user, project, or local settings cannot disable those managed hooks. Only `disableAllHooks` set at the managed settings level can disable managed hooks.453The `disableAllHooks` setting respects the managed settings hierarchy. If an administrator has configured hooks through managed policy settings, `disableAllHooks` set in user, project, or local settings cannot disable those managed hooks. Only `disableAllHooks` set at the managed settings level can disable managed hooks.
385 454
386Direct edits to hooks in settings files don't take effect immediately. Claude Code captures a snapshot of hooks at startup and uses it throughout the session. This prevents malicious or accidental hook modifications from taking effect mid-session without your review. If hooks are modified externally, Claude Code warns you and requires review in the `/hooks` menu before changes apply.455Direct edits to hooks in settings files are normally picked up automatically by the file watcher.
387 456
388## Hook input and output457## Hook input and output
389 458
390Hooks receive JSON data via stdin and communicate results through exit codes, stdout, and stderr. This section covers fields and behavior common to all events. Each event's section under [Hook events](#hook-events) includes its specific input schema and decision control options.459Command hooks receive JSON data via stdin and communicate results through exit codes, stdout, and stderr. HTTP hooks receive the same JSON as the POST request body and communicate results through the HTTP response body. This section covers fields and behavior common to all events. Each event's section under [Hook events](#hook-events) includes its specific input schema and decision control options.
391 460
392### Common input fields461### Common input fields
393 462
394All hook events receive these fields via stdin as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section:463Hook events receive these fields as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section. For command hooks, this JSON arrives via stdin. For HTTP hooks, it arrives as the POST request body.
395 464
396| Field | Description |465| Field | Description |
397| :---------------- | :----------------------------------------------------------------------------------------------------------------------------------------- |466| :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
398| `session_id` | Current session identifier |467| `session_id` | Current session identifier |
399| `transcript_path` | Path to conversation JSON |468| `transcript_path` | Path to conversation JSON |
400| `cwd` | Current working directory when the hook is invoked |469| `cwd` | Current working directory when the hook is invoked |
401| `permission_mode` | Current [permission mode](/en/permissions#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"dontAsk"`, or `"bypassPermissions"` |470| `permission_mode` | Current [permission mode](/en/permissions#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"auto"`, `"dontAsk"`, or `"bypassPermissions"`. Not all events receive this field: see each event's JSON example below to check |
402| `hook_event_name` | Name of the event that fired |471| `hook_event_name` | Name of the event that fired |
403 472
473When running with `--agent` or inside a subagent, two additional fields are included:
474
475| Field | Description |
476| :----------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
477| `agent_id` | Unique identifier for the subagent. Present only when the hook fires inside a subagent call. Use this to distinguish subagent hook calls from main-thread calls. |
478| `agent_type` | Agent name (for example, `"Explore"` or `"security-reviewer"`). Present when the session uses `--agent` or the hook fires inside a subagent. For subagents, the subagent's type takes precedence over the session's `--agent` value. |
479
404For example, a `PreToolUse` hook for a Bash command receives this on stdin:480For example, a `PreToolUse` hook for a Bash command receives this on stdin:
405 481
406```json theme={null}482```json theme={null}
449Exit 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.525Exit 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.
450 526
451| Hook event | Can block? | What happens on exit 2 |527| Hook event | Can block? | What happens on exit 2 |
452| :------------------- | :--------- | :---------------------------------------------------------------------------- |528| :------------------- | :--------- | :----------------------------------------------------------------------------------------------------------------------------------- |
453| `PreToolUse` | Yes | Blocks the tool call |529| `PreToolUse` | Yes | Blocks the tool call |
454| `PermissionRequest` | Yes | Denies the permission |530| `PermissionRequest` | Yes | Denies the permission |
455| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |531| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |
456| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |532| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |
457| `SubagentStop` | Yes | Prevents the subagent from stopping |533| `SubagentStop` | Yes | Prevents the subagent from stopping |
458| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |534| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |
535| `TaskCreated` | Yes | Rolls back the task creation |
459| `TaskCompleted` | Yes | Prevents the task from being marked as completed |536| `TaskCompleted` | Yes | Prevents the task from being marked as completed |
460| `ConfigChange` | Yes | Blocks the configuration change from taking effect (except `policy_settings`) |537| `ConfigChange` | Yes | Blocks the configuration change from taking effect (except `policy_settings`) |
538| `StopFailure` | No | Output and exit code are ignored |
461| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |539| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |
462| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |540| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |
541| `PermissionDenied` | No | Exit code and stderr are ignored (denial already occurred). Use JSON `hookSpecificOutput.retry: true` to tell the model it may retry |
463| `Notification` | No | Shows stderr to user only |542| `Notification` | No | Shows stderr to user only |
464| `SubagentStart` | No | Shows stderr to user only |543| `SubagentStart` | No | Shows stderr to user only |
465| `SessionStart` | No | Shows stderr to user only |544| `SessionStart` | No | Shows stderr to user only |
466| `SessionEnd` | No | Shows stderr to user only |545| `SessionEnd` | No | Shows stderr to user only |
546| `CwdChanged` | No | Shows stderr to user only |
547| `FileChanged` | No | Shows stderr to user only |
467| `PreCompact` | No | Shows stderr to user only |548| `PreCompact` | No | Shows stderr to user only |
549| `PostCompact` | No | Shows stderr to user only |
550| `Elicitation` | Yes | Denies the elicitation |
551| `ElicitationResult` | Yes | Blocks the response (action becomes decline) |
468| `WorktreeCreate` | Yes | Any non-zero exit code causes worktree creation to fail |552| `WorktreeCreate` | Yes | Any non-zero exit code causes worktree creation to fail |
469| `WorktreeRemove` | No | Failures are logged in debug mode only |553| `WorktreeRemove` | No | Failures are logged in debug mode only |
554| `InstructionsLoaded` | No | Exit code is ignored |
555
556### HTTP response handling
557
558HTTP hooks use HTTP status codes and response bodies instead of exit codes and stdout:
559
560* **2xx with an empty body**: success, equivalent to exit code 0 with no output
561* **2xx with a plain text body**: success, the text is added as context
562* **2xx with a JSON body**: success, parsed using the same [JSON output](#json-output) schema as command hooks
563* **Non-2xx status**: non-blocking error, execution continues
564* **Connection failure or timeout**: non-blocking error, execution continues
565
566Unlike command hooks, HTTP hooks cannot signal a blocking error through status codes alone. To block a tool call or deny a permission, return a 2xx response with a JSON body containing the appropriate decision fields.
470 567
471### JSON output568### JSON output
472 569
478 575
479Your hook's stdout must contain only the JSON object. If your shell profile prints text on startup, it can interfere with JSON parsing. See [JSON validation failed](/en/hooks-guide#json-validation-failed) in the troubleshooting guide.576Your hook's stdout must contain only the JSON object. If your shell profile prints text on startup, it can interfere with JSON parsing. See [JSON validation failed](/en/hooks-guide#json-validation-failed) in the troubleshooting guide.
480 577
578Hook output injected into context (`additionalContext`, `systemMessage`, or plain stdout) is capped at 10,000 characters. Output that exceeds this limit is saved to a file and replaced with a preview and file path, the same way large tool results are handled.
579
481The JSON object supports three kinds of fields:580The JSON object supports three kinds of fields:
482 581
483* **Universal fields** like `continue` work across all events. These are listed in the table below.582* **Universal fields** like `continue` work across all events. These are listed in the table below.
502Not 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:601Not 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:
503 602
504| Events | Decision pattern | Key fields |603| Events | Decision pattern | Key fields |
505| :---------------------------------------------------------------------------------- | :------------------- | :-------------------------------------------------------------------------- |604| :-------------------------------------------------------------------------------------------------------------------------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
506| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange | Top-level `decision` | `decision: "block"`, `reason` |605| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange | Top-level `decision` | `decision: "block"`, `reason` |
507| TeammateIdle, TaskCompleted | Exit code only | Exit code 2 blocks the action, stderr is fed back as feedback |606| 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 |
508| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask), `permissionDecisionReason` |607| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask/defer), `permissionDecisionReason` |
509| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |608| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |
510| WorktreeCreate | stdout path | Hook prints absolute path to created worktree. Non-zero exit fails creation |609| PermissionDenied | `hookSpecificOutput` | `retry: true` tells the model it may retry the denied tool call |
511| WorktreeRemove, Notification, SessionEnd, PreCompact | None | No decision control. Used for side effects like logging or cleanup |610| WorktreeCreate | path return | Command hook prints path on stdout; HTTP hook returns `hookSpecificOutput.worktreePath`. Hook failure or missing path fails creation |
611| Elicitation | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values for accept) |
612| ElicitationResult | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values override) |
613| WorktreeRemove, Notification, SessionEnd, PreCompact, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, FileChanged | None | No decision control. Used for side effects like logging or cleanup |
512 614
513Here are examples of each pattern in action:615Here are examples of each pattern in action:
514 616
567 669
568Runs 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.670Runs 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.
569 671
570SessionStart runs on every session, so keep these hooks fast.672SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` hooks are supported.
571 673
572The matcher value corresponds to how the session was initiated:674The matcher value corresponds to how the session was initiated:
573 675
587 "session_id": "abc123",689 "session_id": "abc123",
588 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",690 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
589 "cwd": "/Users/...",691 "cwd": "/Users/...",
590 "permission_mode": "default",
591 "hook_event_name": "SessionStart",692 "hook_event_name": "SessionStart",
592 "source": "startup",693 "source": "startup",
593 "model": "claude-sonnet-4-6"694 "model": "claude-sonnet-4-6"
651Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.752Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.
652 753
653<Note>754<Note>
654 `CLAUDE_ENV_FILE` is available for SessionStart hooks. Other hook types do not have access to this variable.755 `CLAUDE_ENV_FILE` is available for SessionStart, [CwdChanged](#cwdchanged), and [FileChanged](#filechanged) hooks. Other hook types do not have access to this variable.
655</Note>756</Note>
656 757
758### InstructionsLoaded
759
760Fires 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.
761
762The matcher runs against `load_reason`. For example, use `"matcher": "session_start"` to fire only for files loaded at session start, or `"matcher": "path_glob_match|nested_traversal"` to fire only for lazy loads.
763
764#### InstructionsLoaded input
765
766In addition to the [common input fields](#common-input-fields), InstructionsLoaded hooks receive these fields:
767
768| Field | Description |
769| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
770| `file_path` | Absolute path to the instruction file that was loaded |
771| `memory_type` | Scope of the file: `"User"`, `"Project"`, `"Local"`, or `"Managed"` |
772| `load_reason` | Why the file was loaded: `"session_start"`, `"nested_traversal"`, `"path_glob_match"`, `"include"`, or `"compact"`. The `"compact"` value fires when instruction files are re-loaded after a compaction event |
773| `globs` | Path glob patterns from the file's `paths:` frontmatter, if any. Present only for `path_glob_match` loads |
774| `trigger_file_path` | Path to the file whose access triggered this load, for lazy loads |
775| `parent_file_path` | Path to the parent instruction file that included this one, for `include` loads |
776
777```json theme={null}
778{
779 "session_id": "abc123",
780 "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
781 "cwd": "/Users/my-project",
782 "hook_event_name": "InstructionsLoaded",
783 "file_path": "/Users/my-project/CLAUDE.md",
784 "memory_type": "Project",
785 "load_reason": "session_start"
786}
787```
788
789#### InstructionsLoaded decision control
790
791InstructionsLoaded hooks have no decision control. They cannot block or modify instruction loading. Use this event for audit logging, compliance tracking, or observability.
792
657### UserPromptSubmit793### UserPromptSubmit
658 794
659Runs when the user submits a prompt, before Claude processes it. This allows you795Runs when the user submits a prompt, before Claude processes it. This allows you
712 848
713### PreToolUse849### PreToolUse
714 850
715Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name: `Bash`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Task`, `WebFetch`, `WebSearch`, and any [MCP tool names](#match-mcp-tools).851Runs 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).
716 852
717Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.853Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, ask, or defer the tool call.
718 854
719#### PreToolUse input855#### PreToolUse input
720 856
802| `allowed_domains` | array | `["docs.example.com"]` | Optional: only include results from these domains |938| `allowed_domains` | array | `["docs.example.com"]` | Optional: only include results from these domains |
803| `blocked_domains` | array | `["spam.example.com"]` | Optional: exclude results from these domains |939| `blocked_domains` | array | `["spam.example.com"]` | Optional: exclude results from these domains |
804 940
805##### Task941##### Agent
806 942
807Spawns a [subagent](/en/sub-agents).943Spawns a [subagent](/en/sub-agents).
808 944
813| `subagent_type` | string | `"Explore"` | Type of specialized agent to use |949| `subagent_type` | string | `"Explore"` | Type of specialized agent to use |
814| `model` | string | `"sonnet"` | Optional model alias to override the default |950| `model` | string | `"sonnet"` | Optional model alias to override the default |
815 951
952##### AskUserQuestion
953
954Asks the user one to four multiple-choice questions.
955
956| Field | Type | Example | Description |
957| :---------- | :----- | :----------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
958| `questions` | array | `[{"question": "Which framework?", "header": "Framework", "options": [{"label": "React"}], "multiSelect": false}]` | Questions to present, each with a `question` string, short `header`, `options` array, and optional `multiSelect` flag |
959| `answers` | object | `{"Which framework?": "React"}` | Optional. Maps question text to the selected option label. Multi-select answers join labels with commas. Claude does not set this field; supply it via `updatedInput` to answer programmatically |
960
816#### PreToolUse decision control961#### PreToolUse decision control
817 962
818`PreToolUse` hooks can control whether a tool call proceeds. Unlike other hooks that use a top-level `decision` field, PreToolUse returns its decision inside a `hookSpecificOutput` object. This gives it richer control: three outcomes (allow, deny, or ask) plus the ability to modify tool input before execution.963`PreToolUse` hooks can control whether a tool call proceeds. Unlike other hooks that use a top-level `decision` field, PreToolUse returns its decision inside a `hookSpecificOutput` object. This gives it richer control: four outcomes (allow, deny, ask, or defer) plus the ability to modify tool input before execution.
819 964
820| Field | Description |965| Field | Description |
821| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |966| :------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
822| `permissionDecision` | `"allow"` bypasses the permission system, `"deny"` prevents the tool call, `"ask"` prompts the user to confirm |967| `permissionDecision` | `"allow"` skips the permission prompt. `"deny"` prevents the tool call. `"ask"` prompts the user to confirm. `"defer"` exits gracefully so the tool can be resumed later. [Deny and ask rules](/en/permissions#manage-permissions) still apply when a hook returns `"allow"` |
823| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude |968| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude. For `"defer"`, ignored |
824| `updatedInput` | Modifies the tool's input parameters before execution. Combine with `"allow"` to auto-approve, or `"ask"` to show the modified input to the user |969| `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 |
825| `additionalContext` | String added to Claude's context before the tool executes |970| `additionalContext` | String added to Claude's context before the tool executes. For `"defer"`, ignored |
971
972When multiple PreToolUse hooks return different decisions, precedence is `deny` > `defer` > `ask` > `allow`.
973
974When a hook returns `"ask"`, the permission prompt displayed to the user includes a label identifying where the hook came from: for example, `[User]`, `[Project]`, `[Plugin]`, or `[Local]`. This helps users understand which configuration source is requesting confirmation.
826 975
827```json theme={null}976```json theme={null}
828{977{
838}987}
839```988```
840 989
990`AskUserQuestion` and `ExitPlanMode` require user interaction and normally block in [non-interactive mode](/en/headless) with the `-p` flag. Returning `permissionDecision: "allow"` together with `updatedInput` satisfies that requirement: the hook reads the tool's input from stdin, collects the answer through your own UI, and returns it in `updatedInput` so the tool runs without prompting. Returning `"allow"` alone is not sufficient for these tools. For `AskUserQuestion`, echo back the original `questions` array and add an [`answers`](#askuserquestion) object mapping each question's text to the chosen answer.
991
841<Note>992<Note>
842 PreToolUse previously used top-level `decision` and `reason` fields, but these are deprecated for this event. Use `hookSpecificOutput.permissionDecision` and `hookSpecificOutput.permissionDecisionReason` instead. The deprecated values `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively. Other events like PostToolUse and Stop continue to use top-level `decision` and `reason` as their current format.993 PreToolUse previously used top-level `decision` and `reason` fields, but these are deprecated for this event. Use `hookSpecificOutput.permissionDecision` and `hookSpecificOutput.permissionDecisionReason` instead. The deprecated values `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively. Other events like PostToolUse and Stop continue to use top-level `decision` and `reason` as their current format.
843</Note>994</Note>
844 995
996#### Defer a tool call for later
997
998`"defer"` is for integrations that run `claude -p` as a subprocess and read its JSON output, such as an Agent SDK app or a custom UI built on top of Claude Code. It lets that calling process pause Claude at a tool call, collect input through its own interface, and resume where it left off. Claude Code honors this value only in [non-interactive mode](/en/headless) with the `-p` flag. In interactive sessions it logs a warning and ignores the hook result.
999
1000<Note>
1001 The `defer` value requires Claude Code v2.1.89 or later. Earlier versions do not recognize it and the tool proceeds through the normal permission flow.
1002</Note>
1003
1004The `AskUserQuestion` tool is the typical case: Claude wants to ask the user something, but there is no terminal to answer in. The round trip works like this:
1005
10061. Claude calls `AskUserQuestion`. The `PreToolUse` hook fires.
10072. The hook returns `permissionDecision: "defer"`. The tool does not execute. The process exits with `stop_reason: "tool_deferred"` and the pending tool call preserved in the transcript.
10083. The calling process reads `deferred_tool_use` from the SDK result, surfaces the question in its own UI, and waits for an answer.
10094. The calling process runs `claude -p --resume <session-id>`. The same tool call fires `PreToolUse` again.
10105. The hook returns `permissionDecision: "allow"` with the answer in `updatedInput`. The tool executes and Claude continues.
1011
1012The `deferred_tool_use` field carries the tool's `id`, `name`, and `input`. The `input` is the parameters Claude generated for the tool call, captured before execution:
1013
1014```json theme={null}
1015{
1016 "type": "result",
1017 "subtype": "success",
1018 "stop_reason": "tool_deferred",
1019 "session_id": "abc123",
1020 "deferred_tool_use": {
1021 "id": "toolu_01abc",
1022 "name": "AskUserQuestion",
1023 "input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React"}, {"label": "Vue"}], "multiSelect": false }] }
1024 }
1025}
1026```
1027
1028There 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.
1029
1030`"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.
1031
1032If the deferred tool is no longer available when you resume, the process exits with `stop_reason: "tool_deferred_unavailable"` and `is_error: true` before the hook fires. This happens when an MCP server that provided the tool is not connected for the resumed session. The `deferred_tool_use` payload is still included so you can identify which tool went missing.
1033
1034<Warning>
1035 `--resume` does not restore the permission mode from the prior session. Pass the same `--permission-mode` flag on resume that was active when the tool was deferred. Claude Code logs a warning if the modes differ.
1036</Warning>
1037
845### PermissionRequest1038### PermissionRequest
846 1039
847Runs when the user is shown a permission dialog.1040Runs when the user is shown a permission dialog.
866 "description": "Remove node_modules directory"1059 "description": "Remove node_modules directory"
867 },1060 },
868 "permission_suggestions": [1061 "permission_suggestions": [
869 { "type": "toolAlwaysAllow", "tool": "Bash" }1062 {
1063 "type": "addRules",
1064 "rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
1065 "behavior": "allow",
1066 "destination": "localSettings"
1067 }
870 ]1068 ]
871}1069}
872```1070```
876`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:1074`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:
877 1075
878| Field | Description |1076| Field | Description |
879| :------------------- | :------------------------------------------------------------------------------------------------------------- |1077| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
880| `behavior` | `"allow"` grants the permission, `"deny"` denies it |1078| `behavior` | `"allow"` grants the permission, `"deny"` denies it |
881| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution |1079| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones |
882| `updatedPermissions` | For `"allow"` only: applies permission rule updates, equivalent to the user selecting an "always allow" option |1080| `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 |
883| `message` | For `"deny"` only: tells Claude why the permission was denied |1081| `message` | For `"deny"` only: tells Claude why the permission was denied |
884| `interrupt` | For `"deny"` only: if `true`, stops Claude |1082| `interrupt` | For `"deny"` only: if `true`, stops Claude |
885 1083
897}1095}
898```1096```
899 1097
1098#### Permission update entries
1099
1100The `updatedPermissions` output field and the [`permission_suggestions` input field](#permissionrequest-input) both use the same array of entry objects. Each entry has a `type` that determines its other fields, and a `destination` that controls where the change is written.
1101
1102| `type` | Fields | Effect |
1103| :------------------ | :--------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1104| `addRules` | `rules`, `behavior`, `destination` | Adds permission rules. `rules` is an array of `{toolName, ruleContent?}` objects. Omit `ruleContent` to match the whole tool. `behavior` is `"allow"`, `"deny"`, or `"ask"` |
1105| `replaceRules` | `rules`, `behavior`, `destination` | Replaces all rules of the given `behavior` at the `destination` with the provided `rules` |
1106| `removeRules` | `rules`, `behavior`, `destination` | Removes matching rules of the given `behavior` |
1107| `setMode` | `mode`, `destination` | Changes the permission mode. Valid modes are `default`, `acceptEdits`, `dontAsk`, `bypassPermissions`, and `plan` |
1108| `addDirectories` | `directories`, `destination` | Adds working directories. `directories` is an array of path strings |
1109| `removeDirectories` | `directories`, `destination` | Removes working directories |
1110
1111The `destination` field on every entry determines whether the change stays in memory or persists to a settings file.
1112
1113| `destination` | Writes to |
1114| :---------------- | :---------------------------------------------- |
1115| `session` | in-memory only, discarded when the session ends |
1116| `localSettings` | `.claude/settings.local.json` |
1117| `projectSettings` | `.claude/settings.json` |
1118| `userSettings` | `~/.claude/settings.json` |
1119
1120A hook can echo one of the `permission_suggestions` it received as its own `updatedPermissions` output, which is equivalent to the user selecting that "always allow" option in the dialog.
1121
900### PostToolUse1122### PostToolUse
901 1123
902Runs immediately after a tool completes successfully.1124Runs immediately after a tool completes successfully.
999}1221}
1000```1222```
1001 1223
1224### PermissionDenied
1225
1226Runs 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.
1227
1228Matches on tool name, same values as PreToolUse.
1229
1230#### PermissionDenied input
1231
1232In addition to the [common input fields](#common-input-fields), PermissionDenied hooks receive `tool_name`, `tool_input`, `tool_use_id`, and `reason`.
1233
1234```json theme={null}
1235{
1236 "session_id": "abc123",
1237 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1238 "cwd": "/Users/...",
1239 "permission_mode": "auto",
1240 "hook_event_name": "PermissionDenied",
1241 "tool_name": "Bash",
1242 "tool_input": {
1243 "command": "rm -rf /tmp/build",
1244 "description": "Clean build directory"
1245 },
1246 "tool_use_id": "toolu_01ABC123...",
1247 "reason": "Auto mode denied: command targets a path outside the project"
1248}
1249```
1250
1251| Field | Description |
1252| :------- | :------------------------------------------------------------ |
1253| `reason` | The classifier's explanation for why the tool call was denied |
1254
1255#### PermissionDenied decision control
1256
1257PermissionDenied hooks can tell the model it may retry the denied tool call. Return a JSON object with `hookSpecificOutput.retry` set to `true`:
1258
1259```json theme={null}
1260{
1261 "hookSpecificOutput": {
1262 "hookEventName": "PermissionDenied",
1263 "retry": true
1264 }
1265}
1266```
1267
1268When `retry` is `true`, Claude Code adds a message to the conversation telling the model it may retry the tool call. The denial itself is not reversed. If your hook does not return JSON, or returns `retry: false`, the denial stands and the model receives the original rejection message.
1269
1002### Notification1270### Notification
1003 1271
1004Runs 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.1272Runs 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.
1041 "session_id": "abc123",1309 "session_id": "abc123",
1042 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1310 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1043 "cwd": "/Users/...",1311 "cwd": "/Users/...",
1044 "permission_mode": "default",
1045 "hook_event_name": "Notification",1312 "hook_event_name": "Notification",
1046 "message": "Claude needs your permission to use Bash",1313 "message": "Claude needs your permission to use Bash",
1047 "title": "Permission needed",1314 "title": "Permission needed",
1057 1324
1058### SubagentStart1325### SubagentStart
1059 1326
1060Runs when a Claude Code subagent is spawned via the Task tool. Supports matchers to filter by agent type name (built-in agents like `Bash`, `Explore`, `Plan`, or custom agent names from `.claude/agents/`).1327Runs 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/`).
1061 1328
1062#### SubagentStart input1329#### SubagentStart input
1063 1330
1068 "session_id": "abc123",1335 "session_id": "abc123",
1069 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1336 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1070 "cwd": "/Users/...",1337 "cwd": "/Users/...",
1071 "permission_mode": "default",
1072 "hook_event_name": "SubagentStart",1338 "hook_event_name": "SubagentStart",
1073 "agent_id": "agent-abc123",1339 "agent_id": "agent-abc123",
1074 "agent_type": "Explore"1340 "agent_type": "Explore"
1115 1381
1116SubagentStop hooks use the same decision control format as [Stop hooks](#stop-decision-control).1382SubagentStop hooks use the same decision control format as [Stop hooks](#stop-decision-control).
1117 1383
1384### TaskCreated
1385
1386Runs when a task is being created via the `TaskCreate` tool. Use this to enforce naming conventions, require task descriptions, or prevent certain tasks from being created.
1387
1388When a `TaskCreated` hook exits with code 2, the task is not created and the stderr message is fed back to the model as feedback. To stop the teammate entirely instead of re-running it, return JSON with `{"continue": false, "stopReason": "..."}`. TaskCreated hooks do not support matchers and fire on every occurrence.
1389
1390#### TaskCreated input
1391
1392In addition to the [common input fields](#common-input-fields), TaskCreated hooks receive `task_id`, `task_subject`, and optionally `task_description`, `teammate_name`, and `team_name`.
1393
1394```json theme={null}
1395{
1396 "session_id": "abc123",
1397 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1398 "cwd": "/Users/...",
1399 "permission_mode": "default",
1400 "hook_event_name": "TaskCreated",
1401 "task_id": "task-001",
1402 "task_subject": "Implement user authentication",
1403 "task_description": "Add login and signup endpoints",
1404 "teammate_name": "implementer",
1405 "team_name": "my-project"
1406}
1407```
1408
1409| Field | Description |
1410| :----------------- | :---------------------------------------------------- |
1411| `task_id` | Identifier of the task being created |
1412| `task_subject` | Title of the task |
1413| `task_description` | Detailed description of the task. May be absent |
1414| `teammate_name` | Name of the teammate creating the task. May be absent |
1415| `team_name` | Name of the team. May be absent |
1416
1417#### TaskCreated decision control
1418
1419TaskCreated hooks support two ways to control task creation:
1420
1421* **Exit code 2**: the task is not created and the stderr message is fed back to the model as feedback.
1422* **JSON `{"continue": false, "stopReason": "..."}`**: stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user.
1423
1424This example blocks tasks whose subjects don't follow the required format:
1425
1426```bash theme={null}
1427#!/bin/bash
1428INPUT=$(cat)
1429TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
1430
1431if [[ ! "$TASK_SUBJECT" =~ ^\[TICKET-[0-9]+\] ]]; then
1432 echo "Task subject must start with a ticket number, e.g. '[TICKET-123] Add feature'" >&2
1433 exit 2
1434fi
1435
1436exit 0
1437```
1438
1439### TaskCompleted
1440
1441Runs when a task is being marked as completed. This fires in two situations: when any agent explicitly marks a task as completed through the TaskUpdate tool, or when an [agent team](/en/agent-teams) teammate finishes its turn with in-progress tasks. Use this to enforce completion criteria like passing tests or lint checks before a task can close.
1442
1443When a `TaskCompleted` hook exits with code 2, the task is not marked as completed and the stderr message is fed back to the model as feedback. To stop the teammate entirely instead of re-running it, return JSON with `{"continue": false, "stopReason": "..."}`. TaskCompleted hooks do not support matchers and fire on every occurrence.
1444
1445#### TaskCompleted input
1446
1447In addition to the [common input fields](#common-input-fields), TaskCompleted hooks receive `task_id`, `task_subject`, and optionally `task_description`, `teammate_name`, and `team_name`.
1448
1449```json theme={null}
1450{
1451 "session_id": "abc123",
1452 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1453 "cwd": "/Users/...",
1454 "permission_mode": "default",
1455 "hook_event_name": "TaskCompleted",
1456 "task_id": "task-001",
1457 "task_subject": "Implement user authentication",
1458 "task_description": "Add login and signup endpoints",
1459 "teammate_name": "implementer",
1460 "team_name": "my-project"
1461}
1462```
1463
1464| Field | Description |
1465| :----------------- | :------------------------------------------------------ |
1466| `task_id` | Identifier of the task being completed |
1467| `task_subject` | Title of the task |
1468| `task_description` | Detailed description of the task. May be absent |
1469| `teammate_name` | Name of the teammate completing the task. May be absent |
1470| `team_name` | Name of the team. May be absent |
1471
1472#### TaskCompleted decision control
1473
1474TaskCompleted hooks support two ways to control task completion:
1475
1476* **Exit code 2**: the task is not marked as completed and the stderr message is fed back to the model as feedback.
1477* **JSON `{"continue": false, "stopReason": "..."}`**: stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user.
1478
1479This example runs tests and blocks task completion if they fail:
1480
1481```bash theme={null}
1482#!/bin/bash
1483INPUT=$(cat)
1484TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
1485
1486# Run the test suite
1487if ! npm test 2>&1; then
1488 echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
1489 exit 2
1490fi
1491
1492exit 0
1493```
1494
1118### Stop1495### Stop
1119 1496
1120Runs when the main Claude Code agent has finished responding. Does not run if1497Runs when the main Claude Code agent has finished responding. Does not run if
1121the stoppage occurred due to a user interrupt.1498the stoppage occurred due to a user interrupt. API errors fire
1499[StopFailure](#stopfailure) instead.
1122 1500
1123#### Stop input1501#### Stop input
1124 1502
1152}1530}
1153```1531```
1154 1532
1155### TeammateIdle1533### StopFailure
1156 1534
1157Runs when an [agent team](/en/agent-teams) teammate is about to go idle after finishing its turn. Use this to enforce quality gates before a teammate stops working, such as requiring passing lint checks or verifying that output files exist.1535Runs instead of [Stop](#stop) when the turn ends due to an API error. Output and exit code are ignored. Use this to log failures, send alerts, or take recovery actions when Claude cannot complete a response due to rate limits, authentication problems, or other API errors.
1158 1536
1159When a `TeammateIdle` hook exits with code 2, the teammate receives the stderr message as feedback and continues working instead of going idle. TeammateIdle hooks do not support matchers and fire on every occurrence.1537#### StopFailure input
1160 1538
1161#### TeammateIdle input1539In addition to the [common input fields](#common-input-fields), StopFailure hooks receive `error`, optional `error_details`, and optional `last_assistant_message`. The `error` field identifies the error type and is used for matcher filtering.
1162 1540
1163In addition to the [common input fields](#common-input-fields), TeammateIdle hooks receive `teammate_name` and `team_name`.1541| Field | Description |
1542| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1543| `error` | Error type: `rate_limit`, `authentication_failed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, or `unknown` |
1544| `error_details` | Additional details about the error, when available |
1545| `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"` |
1164 1546
1165```json theme={null}1547```json theme={null}
1166{1548{
1167 "session_id": "abc123",1549 "session_id": "abc123",
1168 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1550 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1169 "cwd": "/Users/...",1551 "cwd": "/Users/...",
1170 "permission_mode": "default",1552 "hook_event_name": "StopFailure",
1171 "hook_event_name": "TeammateIdle",1553 "error": "rate_limit",
1172 "teammate_name": "researcher",1554 "error_details": "429 Too Many Requests",
1173 "team_name": "my-project"1555 "last_assistant_message": "API Error: Rate limit reached"
1174}1556}
1175```1557```
1176 1558
1177| Field | Description |1559StopFailure hooks have no decision control. They run for notification and logging purposes only.
1178| :-------------- | :-------------------------------------------- |
1179| `teammate_name` | Name of the teammate that is about to go idle |
1180| `team_name` | Name of the team |
1181
1182#### TeammateIdle decision control
1183
1184TeammateIdle hooks use exit codes only, not JSON decision control. This example checks that a build artifact exists before allowing a teammate to go idle:
1185
1186```bash theme={null}
1187#!/bin/bash
1188 1560
1189if [ ! -f "./dist/output.js" ]; then1561### TeammateIdle
1190 echo "Build artifact missing. Run the build before stopping." >&2
1191 exit 2
1192fi
1193
1194exit 0
1195```
1196
1197### TaskCompleted
1198 1562
1199Runs when a task is being marked as completed. This fires in two situations: when any agent explicitly marks a task as completed through the TaskUpdate tool, or when an [agent team](/en/agent-teams) teammate finishes its turn with in-progress tasks. Use this to enforce completion criteria like passing tests or lint checks before a task can close.1563Runs when an [agent team](/en/agent-teams) teammate is about to go idle after finishing its turn. Use this to enforce quality gates before a teammate stops working, such as requiring passing lint checks or verifying that output files exist.
1200 1564
1201When a `TaskCompleted` hook exits with code 2, the task is not marked as completed and the stderr message is fed back to the model as feedback. TaskCompleted hooks do not support matchers and fire on every occurrence.1565When a `TeammateIdle` hook exits with code 2, the teammate receives the stderr message as feedback and continues working instead of going idle. To stop the teammate entirely instead of re-running it, return JSON with `{"continue": false, "stopReason": "..."}`. TeammateIdle hooks do not support matchers and fire on every occurrence.
1202 1566
1203#### TaskCompleted input1567#### TeammateIdle input
1204 1568
1205In addition to the [common input fields](#common-input-fields), TaskCompleted hooks receive `task_id`, `task_subject`, and optionally `task_description`, `teammate_name`, and `team_name`.1569In addition to the [common input fields](#common-input-fields), TeammateIdle hooks receive `teammate_name` and `team_name`.
1206 1570
1207```json theme={null}1571```json theme={null}
1208{1572{
1210 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1574 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1211 "cwd": "/Users/...",1575 "cwd": "/Users/...",
1212 "permission_mode": "default",1576 "permission_mode": "default",
1213 "hook_event_name": "TaskCompleted",1577 "hook_event_name": "TeammateIdle",
1214 "task_id": "task-001",1578 "teammate_name": "researcher",
1215 "task_subject": "Implement user authentication",
1216 "task_description": "Add login and signup endpoints",
1217 "teammate_name": "implementer",
1218 "team_name": "my-project"1579 "team_name": "my-project"
1219}1580}
1220```1581```
1221 1582
1222| Field | Description |1583| Field | Description |
1223| :----------------- | :------------------------------------------------------ |1584| :-------------- | :-------------------------------------------- |
1224| `task_id` | Identifier of the task being completed |1585| `teammate_name` | Name of the teammate that is about to go idle |
1225| `task_subject` | Title of the task |1586| `team_name` | Name of the team |
1226| `task_description` | Detailed description of the task. May be absent |
1227| `teammate_name` | Name of the teammate completing the task. May be absent |
1228| `team_name` | Name of the team. May be absent |
1229 1587
1230#### TaskCompleted decision control1588#### TeammateIdle decision control
1589
1590TeammateIdle hooks support two ways to control teammate behavior:
1591
1592* **Exit code 2**: the teammate receives the stderr message as feedback and continues working instead of going idle.
1593* **JSON `{"continue": false, "stopReason": "..."}`**: stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user.
1231 1594
1232TaskCompleted hooks use exit codes only, not JSON decision control. This example runs tests and blocks task completion if they fail:1595This example checks that a build artifact exists before allowing a teammate to go idle:
1233 1596
1234```bash theme={null}1597```bash theme={null}
1235#!/bin/bash1598#!/bin/bash
1236INPUT=$(cat)
1237TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
1238 1599
1239# Run the test suite1600if [ ! -f "./dist/output.js" ]; then
1240if ! npm test 2>&1; then1601 echo "Build artifact missing. Run the build before stopping." >&2
1241 echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
1242 exit 21602 exit 2
1243fi1603fi
1244 1604
1289 "session_id": "abc123",1649 "session_id": "abc123",
1290 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1650 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1291 "cwd": "/Users/...",1651 "cwd": "/Users/...",
1292 "permission_mode": "default",
1293 "hook_event_name": "ConfigChange",1652 "hook_event_name": "ConfigChange",
1294 "source": "project_settings",1653 "source": "project_settings",
1295 "file_path": "/Users/.../my-project/.claude/settings.json"1654 "file_path": "/Users/.../my-project/.claude/settings.json"
1314 1673
1315`policy_settings` changes cannot be blocked. Hooks still fire for `policy_settings` sources, so you can use them for audit logging, but any blocking decision is ignored. This ensures enterprise-managed settings always take effect.1674`policy_settings` changes cannot be blocked. Hooks still fire for `policy_settings` sources, so you can use them for audit logging, but any blocking decision is ignored. This ensures enterprise-managed settings always take effect.
1316 1675
1676### CwdChanged
1677
1678Runs 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.
1679
1680CwdChanged 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.
1681
1682CwdChanged does not support matchers and fires on every directory change.
1683
1684#### CwdChanged input
1685
1686In addition to the [common input fields](#common-input-fields), CwdChanged hooks receive `old_cwd` and `new_cwd`.
1687
1688```json theme={null}
1689{
1690 "session_id": "abc123",
1691 "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
1692 "cwd": "/Users/my-project/src",
1693 "hook_event_name": "CwdChanged",
1694 "old_cwd": "/Users/my-project",
1695 "new_cwd": "/Users/my-project/src"
1696}
1697```
1698
1699#### CwdChanged output
1700
1701In addition to the [JSON output fields](#json-output) available to all hooks, CwdChanged hooks can return `watchPaths` to dynamically set which file paths [FileChanged](#filechanged) watches:
1702
1703| Field | Description |
1704| :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1705| `watchPaths` | Array of absolute paths. Replaces the current dynamic watch list (paths from your `matcher` configuration are always watched). Returning an empty array clears the dynamic list, which is typical when entering a new directory |
1706
1707CwdChanged hooks have no decision control. They cannot block the directory change.
1708
1709### FileChanged
1710
1711Runs when a watched file changes on disk. The `matcher` field in your hook configuration controls which filenames to watch: it is a pipe-separated list of basenames (filenames without directory paths, for example `".envrc|.env"`). The same `matcher` value is also used to filter which hooks run when a file changes, matching against the basename of the changed file. Useful for reloading environment variables when project configuration files are modified.
1712
1713FileChanged 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.
1714
1715#### FileChanged input
1716
1717In addition to the [common input fields](#common-input-fields), FileChanged hooks receive `file_path` and `event`.
1718
1719| Field | Description |
1720| :---------- | :---------------------------------------------------------------------------------------------- |
1721| `file_path` | Absolute path to the file that changed |
1722| `event` | What happened: `"change"` (file modified), `"add"` (file created), or `"unlink"` (file deleted) |
1723
1724```json theme={null}
1725{
1726 "session_id": "abc123",
1727 "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
1728 "cwd": "/Users/my-project",
1729 "hook_event_name": "FileChanged",
1730 "file_path": "/Users/my-project/.envrc",
1731 "event": "change"
1732}
1733```
1734
1735#### FileChanged output
1736
1737In addition to the [JSON output fields](#json-output) available to all hooks, FileChanged hooks can return `watchPaths` to dynamically update which file paths are watched:
1738
1739| Field | Description |
1740| :----------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1741| `watchPaths` | Array of absolute paths. Replaces the current dynamic watch list (paths from your `matcher` configuration are always watched). Use this when your hook script discovers additional files to watch based on the changed file |
1742
1743FileChanged hooks have no decision control. They cannot block the file change from occurring.
1744
1317### WorktreeCreate1745### WorktreeCreate
1318 1746
1319When 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.1747When 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.
1320 1748
1321The hook must print the absolute path to the created worktree directory on stdout. Claude Code uses this path as the working directory for the isolated session.1749Because 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.
1750
1751The 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`.
1322 1752
1323This example creates an SVN working copy and prints the path for Claude Code to use. Replace the repository URL with your own:1753This example creates an SVN working copy and prints the path for Claude Code to use. Replace the repository URL with your own:
1324 1754
1357 1787
1358#### WorktreeCreate output1788#### WorktreeCreate output
1359 1789
1360The hook must print the absolute path to the created worktree directory on stdout. If the hook fails or produces no output, worktree creation fails with an error.1790WorktreeCreate hooks do not use the standard allow/block decision model. Instead, the hook's success or failure determines the outcome. The hook must return the absolute path to the created worktree directory:
1791
1792* **Command hooks** (`type: "command"`): print the path on stdout.
1793* **HTTP hooks** (`type: "http"`): return `{ "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } }` in the response body.
1361 1794
1362WorktreeCreate hooks do not use the standard allow/block decision model. Instead, the hook's success or failure determines the outcome. Only `type: "command"` hooks are supported.1795If the hook fails or produces no path, worktree creation fails with an error.
1363 1796
1364### WorktreeRemove1797### WorktreeRemove
1365 1798
1366The cleanup counterpart to [WorktreeCreate](#worktreecreate). This hook fires when a worktree is being removed, either when you exit a `--worktree` session and choose to remove it, or when a subagent with `isolation: "worktree"` finishes. For git-based worktrees, Claude handles cleanup automatically with `git worktree remove`. If you configured a WorktreeCreate hook for a non-git version control system, pair it with a WorktreeRemove hook to handle cleanup. Without one, the worktree directory is left on disk.1799The cleanup counterpart to [WorktreeCreate](#worktreecreate). This hook fires when a worktree is being removed, either when you exit a `--worktree` session and choose to remove it, or when a subagent with `isolation: "worktree"` finishes. For git-based worktrees, Claude handles cleanup automatically with `git worktree remove`. If you configured a WorktreeCreate hook for a non-git version control system, pair it with a WorktreeRemove hook to handle cleanup. Without one, the worktree directory is left on disk.
1367 1800
1368Claude Code passes the path that WorktreeCreate printed on stdout as `worktree_path` in the hook input. This example reads that path and removes the directory:1801Claude Code passes the path returned by WorktreeCreate as `worktree_path` in the hook input. This example reads that path and removes the directory:
1369 1802
1370```json theme={null}1803```json theme={null}
1371{1804{
1398}1831}
1399```1832```
1400 1833
1401WorktreeRemove hooks have no decision control. They cannot block worktree removal but can perform cleanup tasks like removing version control state or archiving changes. Hook failures are logged in debug mode only. Only `type: "command"` hooks are supported.1834WorktreeRemove hooks have no decision control. They cannot block worktree removal but can perform cleanup tasks like removing version control state or archiving changes. Hook failures are logged in debug mode only.
1402 1835
1403### PreCompact1836### PreCompact
1404 1837
1420 "session_id": "abc123",1853 "session_id": "abc123",
1421 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1854 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1422 "cwd": "/Users/...",1855 "cwd": "/Users/...",
1423 "permission_mode": "default",
1424 "hook_event_name": "PreCompact",1856 "hook_event_name": "PreCompact",
1425 "trigger": "manual",1857 "trigger": "manual",
1426 "custom_instructions": ""1858 "custom_instructions": ""
1427}1859}
1428```1860```
1429 1861
1862### PostCompact
1863
1864Runs after Claude Code completes a compact operation. Use this event to react to the new compacted state, for example to log the generated summary or update external state.
1865
1866The same matcher values apply as for `PreCompact`:
1867
1868| Matcher | When it fires |
1869| :------- | :------------------------------------------------- |
1870| `manual` | After `/compact` |
1871| `auto` | After auto-compact when the context window is full |
1872
1873#### PostCompact input
1874
1875In addition to the [common input fields](#common-input-fields), PostCompact hooks receive `trigger` and `compact_summary`. The `compact_summary` field contains the conversation summary generated by the compact operation.
1876
1877```json theme={null}
1878{
1879 "session_id": "abc123",
1880 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1881 "cwd": "/Users/...",
1882 "hook_event_name": "PostCompact",
1883 "trigger": "manual",
1884 "compact_summary": "Summary of the compacted conversation..."
1885}
1886```
1887
1888PostCompact hooks have no decision control. They cannot affect the compaction result but can perform follow-up tasks.
1889
1430### SessionEnd1890### SessionEnd
1431 1891
1432Runs when a Claude Code session ends. Useful for cleanup tasks, logging session1892Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
1437| Reason | Description |1897| Reason | Description |
1438| :---------------------------- | :----------------------------------------- |1898| :---------------------------- | :----------------------------------------- |
1439| `clear` | Session cleared with `/clear` command |1899| `clear` | Session cleared with `/clear` command |
1900| `resume` | Session switched via interactive `/resume` |
1440| `logout` | User logged out |1901| `logout` | User logged out |
1441| `prompt_input_exit` | User exited while prompt input was visible |1902| `prompt_input_exit` | User exited while prompt input was visible |
1442| `bypass_permissions_disabled` | Bypass permissions mode was disabled |1903| `bypass_permissions_disabled` | Bypass permissions mode was disabled |
1451 "session_id": "abc123",1912 "session_id": "abc123",
1452 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1913 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1453 "cwd": "/Users/...",1914 "cwd": "/Users/...",
1454 "permission_mode": "default",
1455 "hook_event_name": "SessionEnd",1915 "hook_event_name": "SessionEnd",
1456 "reason": "other"1916 "reason": "other"
1457}1917}
1459 1919
1460SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.1920SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.
1461 1921
1922SessionEnd hooks have a default timeout of 1.5 seconds. This applies to session exit, `/clear`, and switching sessions via interactive `/resume`. If your hooks need more time, set the `CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS` environment variable to a higher value in milliseconds. Any per-hook `timeout` setting is also capped by this value.
1923
1924```bash theme={null}
1925CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude
1926```
1927
1928### Elicitation
1929
1930Runs when an MCP server requests user input mid-task. By default, Claude Code shows an interactive dialog for the user to respond. Hooks can intercept this request and respond programmatically, skipping the dialog entirely.
1931
1932The matcher field matches against the MCP server name.
1933
1934#### Elicitation input
1935
1936In addition to the [common input fields](#common-input-fields), Elicitation hooks receive `mcp_server_name`, `message`, and optional `mode`, `url`, `elicitation_id`, and `requested_schema` fields.
1937
1938For form-mode elicitation (the most common case):
1939
1940```json theme={null}
1941{
1942 "session_id": "abc123",
1943 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1944 "cwd": "/Users/...",
1945 "permission_mode": "default",
1946 "hook_event_name": "Elicitation",
1947 "mcp_server_name": "my-mcp-server",
1948 "message": "Please provide your credentials",
1949 "mode": "form",
1950 "requested_schema": {
1951 "type": "object",
1952 "properties": {
1953 "username": { "type": "string", "title": "Username" }
1954 }
1955 }
1956}
1957```
1958
1959For URL-mode elicitation (browser-based authentication):
1960
1961```json theme={null}
1962{
1963 "session_id": "abc123",
1964 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1965 "cwd": "/Users/...",
1966 "permission_mode": "default",
1967 "hook_event_name": "Elicitation",
1968 "mcp_server_name": "my-mcp-server",
1969 "message": "Please authenticate",
1970 "mode": "url",
1971 "url": "https://auth.example.com/login"
1972}
1973```
1974
1975#### Elicitation output
1976
1977To respond programmatically without showing the dialog, return a JSON object with `hookSpecificOutput`:
1978
1979```json theme={null}
1980{
1981 "hookSpecificOutput": {
1982 "hookEventName": "Elicitation",
1983 "action": "accept",
1984 "content": {
1985 "username": "alice"
1986 }
1987 }
1988}
1989```
1990
1991| Field | Values | Description |
1992| :-------- | :---------------------------- | :--------------------------------------------------------------- |
1993| `action` | `accept`, `decline`, `cancel` | Whether to accept, decline, or cancel the request |
1994| `content` | object | Form field values to submit. Only used when `action` is `accept` |
1995
1996Exit code 2 denies the elicitation and shows stderr to the user.
1997
1998### ElicitationResult
1999
2000Runs after a user responds to an MCP elicitation. Hooks can observe, modify, or block the response before it is sent back to the MCP server.
2001
2002The matcher field matches against the MCP server name.
2003
2004#### ElicitationResult input
2005
2006In addition to the [common input fields](#common-input-fields), ElicitationResult hooks receive `mcp_server_name`, `action`, and optional `mode`, `elicitation_id`, and `content` fields.
2007
2008```json theme={null}
2009{
2010 "session_id": "abc123",
2011 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
2012 "cwd": "/Users/...",
2013 "permission_mode": "default",
2014 "hook_event_name": "ElicitationResult",
2015 "mcp_server_name": "my-mcp-server",
2016 "action": "accept",
2017 "content": { "username": "alice" },
2018 "mode": "form",
2019 "elicitation_id": "elicit-123"
2020}
2021```
2022
2023#### ElicitationResult output
2024
2025To override the user's response, return a JSON object with `hookSpecificOutput`:
2026
2027```json theme={null}
2028{
2029 "hookSpecificOutput": {
2030 "hookEventName": "ElicitationResult",
2031 "action": "decline",
2032 "content": {}
2033 }
2034}
2035```
2036
2037| Field | Values | Description |
2038| :-------- | :---------------------------- | :--------------------------------------------------------------------- |
2039| `action` | `accept`, `decline`, `cancel` | Overrides the user's action |
2040| `content` | object | Overrides form field values. Only meaningful when `action` is `accept` |
2041
2042Exit code 2 blocks the response, changing the effective action to `decline`.
2043
1462## Prompt-based hooks2044## Prompt-based hooks
1463 2045
1464In addition to Bash command hooks (`type: "command"`), 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.2046In 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.
1465 2047
1466Events that support all three hook types (`command`, `prompt`, and `agent`):2048Events that support all four hook types (`command`, `http`, `prompt`, and `agent`):
1467 2049
1468* `PermissionRequest`2050* `PermissionRequest`
1469* `PostToolUse`2051* `PostToolUse`
1472* `Stop`2054* `Stop`
1473* `SubagentStop`2055* `SubagentStop`
1474* `TaskCompleted`2056* `TaskCompleted`
2057* `TaskCreated`
1475* `UserPromptSubmit`2058* `UserPromptSubmit`
1476 2059
1477Events that only support `type: "command"` hooks:2060Events that support `command` and `http` hooks but not `prompt` or `agent`:
1478 2061
1479* `ConfigChange`2062* `ConfigChange`
2063* `CwdChanged`
2064* `Elicitation`
2065* `ElicitationResult`
2066* `FileChanged`
2067* `InstructionsLoaded`
1480* `Notification`2068* `Notification`
2069* `PermissionDenied`
2070* `PostCompact`
1481* `PreCompact`2071* `PreCompact`
1482* `SessionEnd`2072* `SessionEnd`
1483* `SessionStart`2073* `StopFailure`
1484* `SubagentStart`2074* `SubagentStart`
1485* `TeammateIdle`2075* `TeammateIdle`
1486* `WorktreeCreate`2076* `WorktreeCreate`
1487* `WorktreeRemove`2077* `WorktreeRemove`
1488 2078
2079`SessionStart` supports only `command` hooks.
2080
1489### How prompt-based hooks work2081### How prompt-based hooks work
1490 2082
1491Instead of executing a Bash command, prompt-based hooks:2083Instead of executing a Bash command, prompt-based hooks:
1648 2240
1649After the background process exits, if the hook produced a JSON response with a `systemMessage` or `additionalContext` field, that content is delivered to Claude as context on the next conversation turn.2241After the background process exits, if the hook produced a JSON response with a `systemMessage` or `additionalContext` field, that content is delivered to Claude as context on the next conversation turn.
1650 2242
2243Async hook completion notifications are suppressed by default. To see them, enable verbose mode with `Ctrl+O` or start Claude Code with `--verbose`.
2244
1651### Example: run tests after file changes2245### Example: run tests after file changes
1652 2246
1653This hook starts a test suite in the background whenever Claude writes a file, then reports the results back to Claude when the tests finish. Save this script to `.claude/hooks/run-tests-async.sh` in your project and make it executable with `chmod +x`:2247This hook starts a test suite in the background whenever Claude writes a file, then reports the results back to Claude when the tests finish. Save this script to `.claude/hooks/run-tests-async.sh` in your project and make it executable with `chmod +x`:
1711 2305
1712### Disclaimer2306### Disclaimer
1713 2307
1714Hooks run with your system user's full permissions.2308Command hooks run with your system user's full permissions.
1715 2309
1716<Warning>2310<Warning>
1717 Hooks execute shell commands with your full user permissions. They can modify, delete, or access any files your user account can access. Review and test all hook commands before adding them to your configuration.2311 Command hooks execute shell commands with your full user permissions. They can modify, delete, or access any files your user account can access. Review and test all hook commands before adding them to your configuration.
1718</Warning>2312</Warning>
1719 2313
1720### Security best practices2314### Security best practices
1727* **Use absolute paths**: specify full paths for scripts, using `"$CLAUDE_PROJECT_DIR"` for the project root2321* **Use absolute paths**: specify full paths for scripts, using `"$CLAUDE_PROJECT_DIR"` for the project root
1728* **Skip sensitive files**: avoid `.env`, `.git/`, keys, etc.2322* **Skip sensitive files**: avoid `.env`, `.git/`, keys, etc.
1729 2323
2324## Windows PowerShell tool
2325
2326On Windows, you can run individual hooks in PowerShell by setting `"shell": "powershell"` on a command hook. Hooks spawn PowerShell directly, so this works regardless of whether `CLAUDE_CODE_USE_POWERSHELL_TOOL` is set. Claude Code auto-detects `pwsh.exe` (PowerShell 7+) with a fallback to `powershell.exe` (5.1).
2327
2328```json theme={null}
2329{
2330 "hooks": {
2331 "PostToolUse": [
2332 {
2333 "matcher": "Write",
2334 "hooks": [
2335 {
2336 "type": "command",
2337 "shell": "powershell",
2338 "command": "Write-Host 'File written'"
2339 }
2340 ]
2341 }
2342 ]
2343 }
2344}
2345```
2346
1730## Debug hooks2347## Debug hooks
1731 2348
1732Run `claude --debug` to see hook execution details, including which hooks matched, their exit codes, and output. Toggle verbose mode with `Ctrl+O` to see hook progress in the transcript.2349Run `claude --debug` to see hook execution details, including which hooks matched, their exit codes, and output.
1733 2350
1734```text theme={null}2351```text theme={null}
1735[DEBUG] Executing hooks for PostToolUse:Write2352[DEBUG] Executing hooks for PostToolUse:Write
1736[DEBUG] Getting matching hook commands for PostToolUse with query: Write
1737[DEBUG] Found 1 hook matchers in settings
1738[DEBUG] Matched 1 hooks for query "Write"
1739[DEBUG] Found 1 hook commands to execute2353[DEBUG] Found 1 hook commands to execute
1740[DEBUG] Executing hook command: <Your command> with timeout 600000ms2354[DEBUG] Executing hook command: <Your command> with timeout 600000ms
1741[DEBUG] Hook command completed with status 0: <Your stdout>2355[DEBUG] Hook command completed with status 0: <Your stdout>
1742```2356```
1743 2357
2358For 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.
2359
1744For 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.2360For 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.