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/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=5c25fedbc3db6f8882af50c3cc478c32" alt="Hook lifecycle diagram showing the sequence of hooks from SessionStart through the agentic loop to SessionEnd" data-og-width="8876" width="8876" data-og-height="12492" height="12492" data-path="images/hooks-lifecycle.png" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=280&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=62406fcd5d4a189cc8842ee1bd946b84 280w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=560&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=fa3049022a6973c5f974e0f95b28169d 560w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=840&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=bd2890897db61a03160b93d4f972ff8e 840w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=1100&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=7ae8e098340479347135e39df4a13454 1100w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=1650&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=848a8606aab22c2ccaa16b6a18431e32 1650w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=2500&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=f3a9ef7feb61fa8fe362005aa185efbc 2500w" />21 <img src="https://mintcdn.com/claude-code/1wr0LPds6lVWZkQB/images/hooks-lifecycle.svg?fit=max&auto=format&n=1wr0LPds6lVWZkQB&q=85&s=53a826e7bb64c6bff5f867506c0530ad" 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 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 |
35| `Notification` | When Claude Code sends a notification |35| `Notification` | When Claude Code sends a notification |
36| `SubagentStart` | When a subagent is spawned |36| `SubagentStart` | When a subagent is spawned |
37| `SubagentStop` | When a subagent finishes |37| `SubagentStop` | When a subagent finishes |
38| `TaskCreated` | When a task is being created via `TaskCreate` |
39| `TaskCompleted` | When a task is being marked as completed |
38| `Stop` | When Claude finishes responding |40| `Stop` | When Claude finishes responding |
41| `StopFailure` | When the turn ends due to an API error. Output and exit code are ignored |
42| `TeammateIdle` | When an [agent team](/en/agent-teams) teammate is about to go idle |
43| `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 |
44| `ConfigChange` | When a configuration file changes during a session |
45| `CwdChanged` | When the working directory changes, for example when Claude executes a `cd` command. Useful for reactive environment management with tools like direnv |
46| `FileChanged` | When a watched file changes on disk. The `matcher` field specifies which filenames to watch |
47| `WorktreeCreate` | When a worktree is being created via `--worktree` or `isolation: "worktree"`. Replaces default git behavior |
48| `WorktreeRemove` | When a worktree is being removed, either at session exit or when a subagent finishes |
39| `PreCompact` | Before context compaction |49| `PreCompact` | Before context compaction |
50| `PostCompact` | After context compaction completes |
51| `Elicitation` | When an MCP server requests user input during a tool call |
52| `ElicitationResult` | After a user responds to an MCP elicitation, before the response is sent back to the server |
40| `SessionEnd` | When a session terminates |53| `SessionEnd` | When a session terminates |
41 54
42### How a hook resolves55### How a hook resolves
43 56
44To 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:57To 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:
45 58
46```json theme={null}59```json theme={null}
47{60{
52 "hooks": [65 "hooks": [
53 {66 {
54 "type": "command",67 "type": "command",
55 "command": ".claude/hooks/block-rm.sh"68 "if": "Bash(rm *)",
69 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh"
56 }70 }
57 ]71 ]
58 }72 }
84Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:98Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:
85 99
86<Frame>100<Frame>
87 <img src="https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=7c13f51ffcbc37d22a593b27e2f2de72" 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/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=280&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=36a39a07e8bc1995dcb4639e09846905 280w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=560&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=6568d90c596c7605bbac2c325b0a0c86 560w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=840&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=255a6f68b9475a0e41dbde7b88002dad 840w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=1100&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=dcecf8d5edc88cd2bc49deb006d5760d 1100w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=1650&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=04fe51bf69ae375e9fd517f18674e35f 1650w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=2500&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=b1b76e0b77fddb5c7fa7bf302dacd80b 2500w" />101 <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" />
88</Frame>102</Frame>
89 103
90<Steps>104<Steps>
97 </Step>111 </Step>
98 112
99 <Step title="Matcher checks">113 <Step title="Matcher checks">
100 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.114 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.
115 </Step>
116
117 <Step title="If condition checks">
118 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.
101 </Step>119 </Step>
102 120
103 <Step title="Hook handler runs">121 <Step title="Hook handler runs">
104 The script extracts `"rm -rf /tmp/build"` from the input and finds `rm -rf`, so it prints a decision to stdout:122 The script inspects the full command and finds `rm -rf`, so it prints a decision to stdout:
105 123
106 ```json theme={null}124 ```json theme={null}
107 {125 {
113 }131 }
114 ```132 ```
115 133
116 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.134 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.
117 </Step>135 </Step>
118 136
119 <Step title="Claude Code acts on the result">137 <Step title="Claude Code acts on the result">
134See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.152See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.
135 153
136<Note>154<Note>
137 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.155 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.
138</Note>156</Note>
139 157
140### Hook locations158### Hook locations
157The `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:175The `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:
158 176
159| Event | What the matcher filters | Example matcher values |177| Event | What the matcher filters | Example matcher values |
160| :--------------------------------------------------------------------- | :------------------------ | :----------------------------------------------------------------------------- |178| :------------------------------------------------------------------------------------------------------------- | :-------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ |
161| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |179| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |
162| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |180| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |
163| `SessionEnd` | why the session ended | `clear`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |181| `SessionEnd` | why the session ended | `clear`, `resume`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |
164| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |182| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |
165| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |183| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |
166| `PreCompact` | what triggered compaction | `manual`, `auto` |184| `PreCompact`, `PostCompact` | what triggered compaction | `manual`, `auto` |
167| `SubagentStop` | agent type | same values as `SubagentStart` |185| `SubagentStop` | agent type | same values as `SubagentStart` |
168| `UserPromptSubmit`, `Stop` | no matcher support | always fires on every occurrence |186| `ConfigChange` | configuration source | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` |
187| `CwdChanged` | no matcher support | always fires on every directory change |
188| `FileChanged` | filename (basename of the changed file) | `.envrc`, `.env`, any filename you want to watch |
189| `StopFailure` | error type | `rate_limit`, `authentication_failed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, `unknown` |
190| `InstructionsLoaded` | load reason | `session_start`, `nested_traversal`, `path_glob_match`, `include`, `compact` |
191| `Elicitation` | MCP server name | your configured MCP server names |
192| `ElicitationResult` | MCP server name | same values as `Elicitation` |
193| `UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove` | no matcher support | always fires on every occurrence |
169 194
170The 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.195The 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.
171 196
189}214}
190```215```
191 216
192`UserPromptSubmit` and `Stop` don't support matchers and always fire on every occurrence. If you add a `matcher` field to these events, it is silently ignored.217`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.
218
219For 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.
193 220
194#### Match MCP tools221#### Match MCP tools
195 222
237 264
238### Hook handler fields265### Hook handler fields
239 266
240Each 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:267Each 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:
241 268
242* **[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.269* **[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* **[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.
243* **[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).271* **[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).
244* **[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).272* **[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).
245 273
248These fields apply to all hook types:276These fields apply to all hook types:
249 277
250| Field | Required | Description |278| Field | Required | Description |
251| :-------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |279| :-------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
252| `type` | yes | `"command"`, `"prompt"`, or `"agent"` |280| `type` | yes | `"command"`, `"http"`, `"prompt"`, or `"agent"` |
281| `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`, and `PermissionRequest`. On other events, a hook with `if` set never runs. Uses the same syntax as [permission rules](/en/permissions) |
253| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |282| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |
254| `statusMessage` | no | Custom spinner message displayed while the hook runs |283| `statusMessage` | no | Custom spinner message displayed while the hook runs |
255| `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) |284| `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) |
259In addition to the [common fields](#common-fields), command hooks accept these fields:288In addition to the [common fields](#common-fields), command hooks accept these fields:
260 289
261| Field | Required | Description |290| Field | Required | Description |
262| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------ |291| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
263| `command` | yes | Shell command to execute |292| `command` | yes | Shell command to execute |
264| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |293| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |
294| `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 |
295
296#### HTTP hook fields
297
298In addition to the [common fields](#common-fields), HTTP hooks accept these fields:
299
300| Field | Required | Description |
301| :--------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
302| `url` | yes | URL to send the POST request to |
303| `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 |
304| `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 |
305
306Claude 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.
307
308Error 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"`.
309
310This example sends `PreToolUse` events to a local validation service, authenticating with a token from the `MY_TOKEN` environment variable:
311
312```json theme={null}
313{
314 "hooks": {
315 "PreToolUse": [
316 {
317 "matcher": "Bash",
318 "hooks": [
319 {
320 "type": "http",
321 "url": "http://localhost:8080/hooks/pre-tool-use",
322 "timeout": 30,
323 "headers": {
324 "Authorization": "Bearer $MY_TOKEN"
325 },
326 "allowedEnvVars": ["MY_TOKEN"]
327 }
328 ]
329 }
330 ]
331 }
332}
333```
265 334
266#### Prompt and agent hook fields335#### Prompt and agent hook fields
267 336
272| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |341| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
273| `model` | no | Model to use for evaluation. Defaults to a fast model |342| `model` | no | Model to use for evaluation. Defaults to a fast model |
274 343
275All 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.344All 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.
276 345
277### Reference scripts by path346### Reference scripts by path
278 347
279Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:348Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:
280 349
281* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.350* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.
282* `${CLAUDE_PLUGIN_ROOT}`: the plugin's root directory, for scripts bundled with a [plugin](/en/plugins).351* `${CLAUDE_PLUGIN_ROOT}`: the plugin's installation directory, for scripts bundled with a [plugin](/en/plugins). Changes on each plugin update.
352* `${CLAUDE_PLUGIN_DATA}`: the plugin's [persistent data directory](/en/plugins-reference#persistent-data-directory), for dependencies and state that should survive plugin updates.
283 353
284<Tabs>354<Tabs>
285 <Tab title="Project scripts">355 <Tab title="Project scripts">
360 430
361### The `/hooks` menu431### The `/hooks` menu
362 432
363Type `/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.433Type `/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.
364 434
365Each hook in the menu is labeled with a bracket prefix indicating its source:435The 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:
366 436
367* `[User]`: from `~/.claude/settings.json`437* `User`: from `~/.claude/settings.json`
368* `[Project]`: from `.claude/settings.json`438* `Project`: from `.claude/settings.json`
369* `[Local]`: from `.claude/settings.local.json`439* `Local`: from `.claude/settings.local.json`
370* `[Plugin]`: from a plugin's `hooks/hooks.json`, read-only440* `Plugin`: from a plugin's `hooks/hooks.json`
441* `Session`: registered in memory for the current session
442* `Built-in`: registered internally by Claude Code
443
444Selecting 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.
371 445
372### Disable or remove hooks446### Disable or remove hooks
373 447
374To remove a hook, delete its entry from the settings JSON file, or use the `/hooks` menu and select the hook to delete it.448To remove a hook, delete its entry from the settings JSON file.
449
450To 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.
375 451
376To 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.452The `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.
377 453
378Direct 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.454Direct edits to hooks in settings files are normally picked up automatically by the file watcher.
379 455
380## Hook input and output456## Hook input and output
381 457
382Hooks 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.458Command 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.
383 459
384### Common input fields460### Common input fields
385 461
386All hook events receive these fields via stdin as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section:462Hook 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.
387 463
388| Field | Description |464| Field | Description |
389| :---------------- | :--------------------------------------------------------------------------------------------------------------------------------- |465| :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
390| `session_id` | Current session identifier |466| `session_id` | Current session identifier |
391| `transcript_path` | Path to conversation JSON |467| `transcript_path` | Path to conversation JSON |
392| `cwd` | Current working directory when the hook is invoked |468| `cwd` | Current working directory when the hook is invoked |
393| `permission_mode` | Current [permission mode](/en/iam#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"dontAsk"`, or `"bypassPermissions"` |469| `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 |
394| `hook_event_name` | Name of the event that fired |470| `hook_event_name` | Name of the event that fired |
395 471
472When running with `--agent` or inside a subagent, two additional fields are included:
473
474| Field | Description |
475| :----------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
476| `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. |
477| `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. |
478
396For example, a `PreToolUse` hook for a Bash command receives this on stdin:479For example, a `PreToolUse` hook for a Bash command receives this on stdin:
397 480
398```json theme={null}481```json theme={null}
441Exit 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.524Exit 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.
442 525
443| Hook event | Can block? | What happens on exit 2 |526| Hook event | Can block? | What happens on exit 2 |
444| :------------------- | :--------- | :-------------------------------------------------------- |527| :------------------- | :--------- | :---------------------------------------------------------------------------- |
445| `PreToolUse` | Yes | Blocks the tool call |528| `PreToolUse` | Yes | Blocks the tool call |
446| `PermissionRequest` | Yes | Denies the permission |529| `PermissionRequest` | Yes | Denies the permission |
447| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |530| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |
448| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |531| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |
449| `SubagentStop` | Yes | Prevents the subagent from stopping |532| `SubagentStop` | Yes | Prevents the subagent from stopping |
533| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |
534| `TaskCreated` | Yes | Prevents the task from being created |
535| `TaskCompleted` | Yes | Prevents the task from being marked as completed |
536| `ConfigChange` | Yes | Blocks the configuration change from taking effect (except `policy_settings`) |
537| `StopFailure` | No | Output and exit code are ignored |
450| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |538| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |
451| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |539| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |
452| `Notification` | No | Shows stderr to user only |540| `Notification` | No | Shows stderr to user only |
453| `SubagentStart` | No | Shows stderr to user only |541| `SubagentStart` | No | Shows stderr to user only |
454| `SessionStart` | No | Shows stderr to user only |542| `SessionStart` | No | Shows stderr to user only |
455| `SessionEnd` | No | Shows stderr to user only |543| `SessionEnd` | No | Shows stderr to user only |
544| `CwdChanged` | No | Shows stderr to user only |
545| `FileChanged` | No | Shows stderr to user only |
456| `PreCompact` | No | Shows stderr to user only |546| `PreCompact` | No | Shows stderr to user only |
547| `PostCompact` | No | Shows stderr to user only |
548| `Elicitation` | Yes | Denies the elicitation |
549| `ElicitationResult` | Yes | Blocks the response (action becomes decline) |
550| `WorktreeCreate` | Yes | Any non-zero exit code causes worktree creation to fail |
551| `WorktreeRemove` | No | Failures are logged in debug mode only |
552| `InstructionsLoaded` | No | Exit code is ignored |
553
554### HTTP response handling
555
556HTTP hooks use HTTP status codes and response bodies instead of exit codes and stdout:
557
558* **2xx with an empty body**: success, equivalent to exit code 0 with no output
559* **2xx with a plain text body**: success, the text is added as context
560* **2xx with a JSON body**: success, parsed using the same [JSON output](#json-output) schema as command hooks
561* **Non-2xx status**: non-blocking error, execution continues
562* **Connection failure or timeout**: non-blocking error, execution continues
563
564Unlike 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.
457 565
458### JSON output566### JSON output
459 567
489Not 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:597Not 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:
490 598
491| Events | Decision pattern | Key fields |599| Events | Decision pattern | Key fields |
492| :-------------------------------------------------------------------- | :------------------- | :---------------------------------------------------------------- |600| :-------------------------------------------------------------------------------------------------------------------------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
493| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop | Top-level `decision` | `decision: "block"`, `reason` |601| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange | Top-level `decision` | `decision: "block"`, `reason` |
602| 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 |
494| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask), `permissionDecisionReason` |603| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask), `permissionDecisionReason` |
495| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |604| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |
605| WorktreeCreate | path return | Command hook prints path on stdout; HTTP hook returns `hookSpecificOutput.worktreePath`. Hook failure or missing path fails creation |
606| Elicitation | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values for accept) |
607| ElicitationResult | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values override) |
608| WorktreeRemove, Notification, SessionEnd, PreCompact, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, FileChanged | None | No decision control. Used for side effects like logging or cleanup |
496 609
497Here are examples of each pattern in action:610Here are examples of each pattern in action:
498 611
499<Tabs>612<Tabs>
500 <Tab title="Top-level decision">613 <Tab title="Top-level decision">
501 Used by `UserPromptSubmit`, `PostToolUse`, `PostToolUseFailure`, `Stop`, and `SubagentStop`. The only value is `"block"` — to allow the action to proceed, omit `decision` from your JSON, or exit 0 without any JSON at all:614 Used by `UserPromptSubmit`, `PostToolUse`, `PostToolUseFailure`, `Stop`, `SubagentStop`, and `ConfigChange`. The only value is `"block"`. To allow the action to proceed, omit `decision` from your JSON, or exit 0 without any JSON at all:
502 615
503 ```json theme={null}616 ```json theme={null}
504 {617 {
551 664
552Runs 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.665Runs 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.
553 666
554SessionStart runs on every session, so keep these hooks fast.667SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` hooks are supported.
555 668
556The matcher value corresponds to how the session was initiated:669The matcher value corresponds to how the session was initiated:
557 670
571 "session_id": "abc123",684 "session_id": "abc123",
572 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",685 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
573 "cwd": "/Users/...",686 "cwd": "/Users/...",
574 "permission_mode": "default",
575 "hook_event_name": "SessionStart",687 "hook_event_name": "SessionStart",
576 "source": "startup",688 "source": "startup",
577 "model": "claude-sonnet-4-5-20250929"689 "model": "claude-sonnet-4-6"
578}690}
579```691```
580 692
635Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.747Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.
636 748
637<Note>749<Note>
638 `CLAUDE_ENV_FILE` is available for SessionStart hooks. Other hook types do not have access to this variable.750 `CLAUDE_ENV_FILE` is available for SessionStart, [CwdChanged](#cwdchanged), and [FileChanged](#filechanged) hooks. Other hook types do not have access to this variable.
639</Note>751</Note>
640 752
753### InstructionsLoaded
754
755Fires 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.
756
757The 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.
758
759#### InstructionsLoaded input
760
761In addition to the [common input fields](#common-input-fields), InstructionsLoaded hooks receive these fields:
762
763| Field | Description |
764| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
765| `file_path` | Absolute path to the instruction file that was loaded |
766| `memory_type` | Scope of the file: `"User"`, `"Project"`, `"Local"`, or `"Managed"` |
767| `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 |
768| `globs` | Path glob patterns from the file's `paths:` frontmatter, if any. Present only for `path_glob_match` loads |
769| `trigger_file_path` | Path to the file whose access triggered this load, for lazy loads |
770| `parent_file_path` | Path to the parent instruction file that included this one, for `include` loads |
771
772```json theme={null}
773{
774 "session_id": "abc123",
775 "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
776 "cwd": "/Users/my-project",
777 "hook_event_name": "InstructionsLoaded",
778 "file_path": "/Users/my-project/CLAUDE.md",
779 "memory_type": "Project",
780 "load_reason": "session_start"
781}
782```
783
784#### InstructionsLoaded decision control
785
786InstructionsLoaded hooks have no decision control. They cannot block or modify instruction loading. Use this event for audit logging, compliance tracking, or observability.
787
641### UserPromptSubmit788### UserPromptSubmit
642 789
643Runs when the user submits a prompt, before Claude processes it. This allows you790Runs when the user submits a prompt, before Claude processes it. This allows you
696 843
697### PreToolUse844### PreToolUse
698 845
699Runs 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).846Runs 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).
700 847
701Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.848Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.
702 849
786| `allowed_domains` | array | `["docs.example.com"]` | Optional: only include results from these domains |933| `allowed_domains` | array | `["docs.example.com"]` | Optional: only include results from these domains |
787| `blocked_domains` | array | `["spam.example.com"]` | Optional: exclude results from these domains |934| `blocked_domains` | array | `["spam.example.com"]` | Optional: exclude results from these domains |
788 935
789##### Task936##### Agent
790 937
791Spawns a [subagent](/en/sub-agents).938Spawns a [subagent](/en/sub-agents).
792 939
797| `subagent_type` | string | `"Explore"` | Type of specialized agent to use |944| `subagent_type` | string | `"Explore"` | Type of specialized agent to use |
798| `model` | string | `"sonnet"` | Optional model alias to override the default |945| `model` | string | `"sonnet"` | Optional model alias to override the default |
799 946
947##### AskUserQuestion
948
949Asks the user one to four multiple-choice questions.
950
951| Field | Type | Example | Description |
952| :---------- | :----- | :----------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
953| `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 |
954| `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 |
955
800#### PreToolUse decision control956#### PreToolUse decision control
801 957
802`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.958`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.
803 959
804| Field | Description |960| Field | Description |
805| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |961| :------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
806| `permissionDecision` | `"allow"` bypasses the permission system, `"deny"` prevents the tool call, `"ask"` prompts the user to confirm |962| `permissionDecision` | `"allow"` skips the permission prompt. `"deny"` prevents the tool call. `"ask"` prompts the user to confirm. [Deny and ask rules](/en/permissions#manage-permissions) still apply when a hook returns `"allow"` |
807| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude |963| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude |
808| `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 |964| `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 |
809| `additionalContext` | String added to Claude's context before the tool executes |965| `additionalContext` | String added to Claude's context before the tool executes |
810 966
967When 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.
968
811```json theme={null}969```json theme={null}
812{970{
813 "hookSpecificOutput": {971 "hookSpecificOutput": {
822}980}
823```981```
824 982
983`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.
984
825<Note>985<Note>
826 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.986 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.
827</Note>987</Note>
850 "description": "Remove node_modules directory"1010 "description": "Remove node_modules directory"
851 },1011 },
852 "permission_suggestions": [1012 "permission_suggestions": [
853 { "type": "toolAlwaysAllow", "tool": "Bash" }1013 {
1014 "type": "addRules",
1015 "rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
1016 "behavior": "allow",
1017 "destination": "localSettings"
1018 }
854 ]1019 ]
855}1020}
856```1021```
860`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:1025`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:
861 1026
862| Field | Description |1027| Field | Description |
863| :------------------- | :------------------------------------------------------------------------------------------------------------- |1028| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
864| `behavior` | `"allow"` grants the permission, `"deny"` denies it |1029| `behavior` | `"allow"` grants the permission, `"deny"` denies it |
865| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution |1030| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones |
866| `updatedPermissions` | For `"allow"` only: applies permission rule updates, equivalent to the user selecting an "always allow" option |1031| `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 |
867| `message` | For `"deny"` only: tells Claude why the permission was denied |1032| `message` | For `"deny"` only: tells Claude why the permission was denied |
868| `interrupt` | For `"deny"` only: if `true`, stops Claude |1033| `interrupt` | For `"deny"` only: if `true`, stops Claude |
869 1034
881}1046}
882```1047```
883 1048
1049#### Permission update entries
1050
1051The `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.
1052
1053| `type` | Fields | Effect |
1054| :------------------ | :--------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1055| `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"` |
1056| `replaceRules` | `rules`, `behavior`, `destination` | Replaces all rules of the given `behavior` at the `destination` with the provided `rules` |
1057| `removeRules` | `rules`, `behavior`, `destination` | Removes matching rules of the given `behavior` |
1058| `setMode` | `mode`, `destination` | Changes the permission mode. Valid modes are `default`, `acceptEdits`, `dontAsk`, `bypassPermissions`, and `plan` |
1059| `addDirectories` | `directories`, `destination` | Adds working directories. `directories` is an array of path strings |
1060| `removeDirectories` | `directories`, `destination` | Removes working directories |
1061
1062The `destination` field on every entry determines whether the change stays in memory or persists to a settings file.
1063
1064| `destination` | Writes to |
1065| :---------------- | :---------------------------------------------- |
1066| `session` | in-memory only, discarded when the session ends |
1067| `localSettings` | `.claude/settings.local.json` |
1068| `projectSettings` | `.claude/settings.json` |
1069| `userSettings` | `~/.claude/settings.json` |
1070
1071A 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.
1072
884### PostToolUse1073### PostToolUse
885 1074
886Runs immediately after a tool completes successfully.1075Runs immediately after a tool completes successfully.
1025 "session_id": "abc123",1214 "session_id": "abc123",
1026 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1215 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1027 "cwd": "/Users/...",1216 "cwd": "/Users/...",
1028 "permission_mode": "default",
1029 "hook_event_name": "Notification",1217 "hook_event_name": "Notification",
1030 "message": "Claude needs your permission to use Bash",1218 "message": "Claude needs your permission to use Bash",
1031 "title": "Permission needed",1219 "title": "Permission needed",
1041 1229
1042### SubagentStart1230### SubagentStart
1043 1231
1044Runs 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/`).1232Runs 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/`).
1045 1233
1046#### SubagentStart input1234#### SubagentStart input
1047 1235
1052 "session_id": "abc123",1240 "session_id": "abc123",
1053 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1241 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1054 "cwd": "/Users/...",1242 "cwd": "/Users/...",
1055 "permission_mode": "default",
1056 "hook_event_name": "SubagentStart",1243 "hook_event_name": "SubagentStart",
1057 "agent_id": "agent-abc123",1244 "agent_id": "agent-abc123",
1058 "agent_type": "Explore"1245 "agent_type": "Explore"
1080 1267
1081#### SubagentStop input1268#### SubagentStop input
1082 1269
1083In addition to the [common input fields](#common-input-fields), SubagentStop hooks receive `stop_hook_active`, `agent_id`, `agent_type`, and `agent_transcript_path`. The `agent_type` field is the value used for matcher filtering. The `transcript_path` is the main session's transcript, while `agent_transcript_path` is the subagent's own transcript stored in a nested `subagents/` folder.1270In addition to the [common input fields](#common-input-fields), SubagentStop hooks receive `stop_hook_active`, `agent_id`, `agent_type`, `agent_transcript_path`, and `last_assistant_message`. The `agent_type` field is the value used for matcher filtering. The `transcript_path` is the main session's transcript, while `agent_transcript_path` is the subagent's own transcript stored in a nested `subagents/` folder. The `last_assistant_message` field contains the text content of the subagent's final response, so hooks can access it without parsing the transcript file.
1084 1271
1085```json theme={null}1272```json theme={null}
1086{1273{
1092 "stop_hook_active": false,1279 "stop_hook_active": false,
1093 "agent_id": "def456",1280 "agent_id": "def456",
1094 "agent_type": "Explore",1281 "agent_type": "Explore",
1095 "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl"1282 "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
1283 "last_assistant_message": "Analysis complete. Found 3 potential issues..."
1096}1284}
1097```1285```
1098 1286
1099SubagentStop hooks use the same decision control format as [Stop hooks](#stop-decision-control).1287SubagentStop hooks use the same decision control format as [Stop hooks](#stop-decision-control).
1100 1288
1289### TaskCreated
1290
1291Runs 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.
1292
1293When 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.
1294
1295#### TaskCreated input
1296
1297In 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`.
1298
1299```json theme={null}
1300{
1301 "session_id": "abc123",
1302 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1303 "cwd": "/Users/...",
1304 "permission_mode": "default",
1305 "hook_event_name": "TaskCreated",
1306 "task_id": "task-001",
1307 "task_subject": "Implement user authentication",
1308 "task_description": "Add login and signup endpoints",
1309 "teammate_name": "implementer",
1310 "team_name": "my-project"
1311}
1312```
1313
1314| Field | Description |
1315| :----------------- | :---------------------------------------------------- |
1316| `task_id` | Identifier of the task being created |
1317| `task_subject` | Title of the task |
1318| `task_description` | Detailed description of the task. May be absent |
1319| `teammate_name` | Name of the teammate creating the task. May be absent |
1320| `team_name` | Name of the team. May be absent |
1321
1322#### TaskCreated decision control
1323
1324TaskCreated hooks support two ways to control task creation:
1325
1326* **Exit code 2**: the task is not created and the stderr message is fed back to the model as feedback.
1327* **JSON `{"continue": false, "stopReason": "..."}`**: stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user.
1328
1329This example blocks tasks whose subjects don't follow the required format:
1330
1331```bash theme={null}
1332#!/bin/bash
1333INPUT=$(cat)
1334TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
1335
1336if [[ ! "$TASK_SUBJECT" =~ ^\[TICKET-[0-9]+\] ]]; then
1337 echo "Task subject must start with a ticket number, e.g. '[TICKET-123] Add feature'" >&2
1338 exit 2
1339fi
1340
1341exit 0
1342```
1343
1344### TaskCompleted
1345
1346Runs 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.
1347
1348When 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.
1349
1350#### TaskCompleted input
1351
1352In 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`.
1353
1354```json theme={null}
1355{
1356 "session_id": "abc123",
1357 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1358 "cwd": "/Users/...",
1359 "permission_mode": "default",
1360 "hook_event_name": "TaskCompleted",
1361 "task_id": "task-001",
1362 "task_subject": "Implement user authentication",
1363 "task_description": "Add login and signup endpoints",
1364 "teammate_name": "implementer",
1365 "team_name": "my-project"
1366}
1367```
1368
1369| Field | Description |
1370| :----------------- | :------------------------------------------------------ |
1371| `task_id` | Identifier of the task being completed |
1372| `task_subject` | Title of the task |
1373| `task_description` | Detailed description of the task. May be absent |
1374| `teammate_name` | Name of the teammate completing the task. May be absent |
1375| `team_name` | Name of the team. May be absent |
1376
1377#### TaskCompleted decision control
1378
1379TaskCompleted hooks support two ways to control task completion:
1380
1381* **Exit code 2**: the task is not marked as completed and the stderr message is fed back to the model as feedback.
1382* **JSON `{"continue": false, "stopReason": "..."}`**: stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user.
1383
1384This example runs tests and blocks task completion if they fail:
1385
1386```bash theme={null}
1387#!/bin/bash
1388INPUT=$(cat)
1389TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
1390
1391# Run the test suite
1392if ! npm test 2>&1; then
1393 echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
1394 exit 2
1395fi
1396
1397exit 0
1398```
1399
1101### Stop1400### Stop
1102 1401
1103Runs when the main Claude Code agent has finished responding. Does not run if1402Runs when the main Claude Code agent has finished responding. Does not run if
1104the stoppage occurred due to a user interrupt.1403the stoppage occurred due to a user interrupt. API errors fire
1404[StopFailure](#stopfailure) instead.
1105 1405
1106#### Stop input1406#### Stop input
1107 1407
1108In addition to the [common input fields](#common-input-fields), Stop hooks receive `stop_hook_active`. This field is `true` when Claude Code is already continuing as a result of a stop hook. Check this value or process the transcript to prevent Claude Code from running indefinitely.1408In addition to the [common input fields](#common-input-fields), Stop hooks receive `stop_hook_active` and `last_assistant_message`. The `stop_hook_active` field is `true` when Claude Code is already continuing as a result of a stop hook. Check this value or process the transcript to prevent Claude Code from running indefinitely. The `last_assistant_message` field contains the text content of Claude's final response, so hooks can access it without parsing the transcript file.
1109 1409
1110```json theme={null}1410```json theme={null}
1111{1411{
1114 "cwd": "/Users/...",1414 "cwd": "/Users/...",
1115 "permission_mode": "default",1415 "permission_mode": "default",
1116 "hook_event_name": "Stop",1416 "hook_event_name": "Stop",
1117 "stop_hook_active": true1417 "stop_hook_active": true,
1418 "last_assistant_message": "I've completed the refactoring. Here's a summary..."
1118}1419}
1119```1420```
1120 1421
1134}1435}
1135```1436```
1136 1437
1438### StopFailure
1439
1440Runs 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.
1441
1442#### StopFailure input
1443
1444In 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.
1445
1446| Field | Description |
1447| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1448| `error` | Error type: `rate_limit`, `authentication_failed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, or `unknown` |
1449| `error_details` | Additional details about the error, when available |
1450| `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"` |
1451
1452```json theme={null}
1453{
1454 "session_id": "abc123",
1455 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1456 "cwd": "/Users/...",
1457 "hook_event_name": "StopFailure",
1458 "error": "rate_limit",
1459 "error_details": "429 Too Many Requests",
1460 "last_assistant_message": "API Error: Rate limit reached"
1461}
1462```
1463
1464StopFailure hooks have no decision control. They run for notification and logging purposes only.
1465
1466### TeammateIdle
1467
1468Runs 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.
1469
1470When 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.
1471
1472#### TeammateIdle input
1473
1474In addition to the [common input fields](#common-input-fields), TeammateIdle hooks receive `teammate_name` and `team_name`.
1475
1476```json theme={null}
1477{
1478 "session_id": "abc123",
1479 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1480 "cwd": "/Users/...",
1481 "permission_mode": "default",
1482 "hook_event_name": "TeammateIdle",
1483 "teammate_name": "researcher",
1484 "team_name": "my-project"
1485}
1486```
1487
1488| Field | Description |
1489| :-------------- | :-------------------------------------------- |
1490| `teammate_name` | Name of the teammate that is about to go idle |
1491| `team_name` | Name of the team |
1492
1493#### TeammateIdle decision control
1494
1495TeammateIdle hooks support two ways to control teammate behavior:
1496
1497* **Exit code 2**: the teammate receives the stderr message as feedback and continues working instead of going idle.
1498* **JSON `{"continue": false, "stopReason": "..."}`**: stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user.
1499
1500This example checks that a build artifact exists before allowing a teammate to go idle:
1501
1502```bash theme={null}
1503#!/bin/bash
1504
1505if [ ! -f "./dist/output.js" ]; then
1506 echo "Build artifact missing. Run the build before stopping." >&2
1507 exit 2
1508fi
1509
1510exit 0
1511```
1512
1513### ConfigChange
1514
1515Runs when a configuration file changes during a session. Use this to audit settings changes, enforce security policies, or block unauthorized modifications to configuration files.
1516
1517ConfigChange hooks fire for changes to settings files, managed policy settings, and skill files. The `source` field in the input tells you which type of configuration changed, and the optional `file_path` field provides the path to the changed file.
1518
1519The matcher filters on the configuration source:
1520
1521| Matcher | When it fires |
1522| :----------------- | :---------------------------------------- |
1523| `user_settings` | `~/.claude/settings.json` changes |
1524| `project_settings` | `.claude/settings.json` changes |
1525| `local_settings` | `.claude/settings.local.json` changes |
1526| `policy_settings` | Managed policy settings change |
1527| `skills` | A skill file in `.claude/skills/` changes |
1528
1529This example logs all configuration changes for security auditing:
1530
1531```json theme={null}
1532{
1533 "hooks": {
1534 "ConfigChange": [
1535 {
1536 "hooks": [
1537 {
1538 "type": "command",
1539 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit-config-change.sh"
1540 }
1541 ]
1542 }
1543 ]
1544 }
1545}
1546```
1547
1548#### ConfigChange input
1549
1550In addition to the [common input fields](#common-input-fields), ConfigChange hooks receive `source` and optionally `file_path`. The `source` field indicates which configuration type changed, and `file_path` provides the path to the specific file that was modified.
1551
1552```json theme={null}
1553{
1554 "session_id": "abc123",
1555 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1556 "cwd": "/Users/...",
1557 "hook_event_name": "ConfigChange",
1558 "source": "project_settings",
1559 "file_path": "/Users/.../my-project/.claude/settings.json"
1560}
1561```
1562
1563#### ConfigChange decision control
1564
1565ConfigChange hooks can block configuration changes from taking effect. Use exit code 2 or a JSON `decision` to prevent the change. When blocked, the new settings are not applied to the running session.
1566
1567| Field | Description |
1568| :--------- | :--------------------------------------------------------------------------------------- |
1569| `decision` | `"block"` prevents the configuration change from being applied. Omit to allow the change |
1570| `reason` | Explanation shown to the user when `decision` is `"block"` |
1571
1572```json theme={null}
1573{
1574 "decision": "block",
1575 "reason": "Configuration changes to project settings require admin approval"
1576}
1577```
1578
1579`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.
1580
1581### CwdChanged
1582
1583Runs 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.
1584
1585CwdChanged 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.
1586
1587CwdChanged does not support matchers and fires on every directory change.
1588
1589#### CwdChanged input
1590
1591In addition to the [common input fields](#common-input-fields), CwdChanged hooks receive `old_cwd` and `new_cwd`.
1592
1593```json theme={null}
1594{
1595 "session_id": "abc123",
1596 "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
1597 "cwd": "/Users/my-project/src",
1598 "hook_event_name": "CwdChanged",
1599 "old_cwd": "/Users/my-project",
1600 "new_cwd": "/Users/my-project/src"
1601}
1602```
1603
1604#### CwdChanged output
1605
1606In 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:
1607
1608| Field | Description |
1609| :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1610| `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 |
1611
1612CwdChanged hooks have no decision control. They cannot block the directory change.
1613
1614### FileChanged
1615
1616Runs 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.
1617
1618FileChanged 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.
1619
1620#### FileChanged input
1621
1622In addition to the [common input fields](#common-input-fields), FileChanged hooks receive `file_path` and `event`.
1623
1624| Field | Description |
1625| :---------- | :---------------------------------------------------------------------------------------------- |
1626| `file_path` | Absolute path to the file that changed |
1627| `event` | What happened: `"change"` (file modified), `"add"` (file created), or `"unlink"` (file deleted) |
1628
1629```json theme={null}
1630{
1631 "session_id": "abc123",
1632 "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
1633 "cwd": "/Users/my-project",
1634 "hook_event_name": "FileChanged",
1635 "file_path": "/Users/my-project/.envrc",
1636 "event": "change"
1637}
1638```
1639
1640#### FileChanged output
1641
1642In 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:
1643
1644| Field | Description |
1645| :----------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1646| `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 |
1647
1648FileChanged hooks have no decision control. They cannot block the file change from occurring.
1649
1650### WorktreeCreate
1651
1652When 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.
1653
1654Because 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.
1655
1656The 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`.
1657
1658This example creates an SVN working copy and prints the path for Claude Code to use. Replace the repository URL with your own:
1659
1660```json theme={null}
1661{
1662 "hooks": {
1663 "WorktreeCreate": [
1664 {
1665 "hooks": [
1666 {
1667 "type": "command",
1668 "command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
1669 }
1670 ]
1671 }
1672 ]
1673 }
1674}
1675```
1676
1677The hook reads the worktree `name` from the JSON input on stdin, checks out a fresh copy into a new directory, and prints the directory path. The `echo` on the last line is what Claude Code reads as the worktree path. Redirect any other output to stderr so it doesn't interfere with the path.
1678
1679#### WorktreeCreate input
1680
1681In addition to the [common input fields](#common-input-fields), WorktreeCreate hooks receive the `name` field. This is a slug identifier for the new worktree, either specified by the user or auto-generated (for example, `bold-oak-a3f2`).
1682
1683```json theme={null}
1684{
1685 "session_id": "abc123",
1686 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1687 "cwd": "/Users/...",
1688 "hook_event_name": "WorktreeCreate",
1689 "name": "feature-auth"
1690}
1691```
1692
1693#### WorktreeCreate output
1694
1695WorktreeCreate 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:
1696
1697* **Command hooks** (`type: "command"`): print the path on stdout.
1698* **HTTP hooks** (`type: "http"`): return `{ "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } }` in the response body.
1699
1700If the hook fails or produces no path, worktree creation fails with an error.
1701
1702### WorktreeRemove
1703
1704The 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.
1705
1706Claude Code passes the path returned by WorktreeCreate as `worktree_path` in the hook input. This example reads that path and removes the directory:
1707
1708```json theme={null}
1709{
1710 "hooks": {
1711 "WorktreeRemove": [
1712 {
1713 "hooks": [
1714 {
1715 "type": "command",
1716 "command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
1717 }
1718 ]
1719 }
1720 ]
1721 }
1722}
1723```
1724
1725#### WorktreeRemove input
1726
1727In addition to the [common input fields](#common-input-fields), WorktreeRemove hooks receive the `worktree_path` field, which is the absolute path to the worktree being removed.
1728
1729```json theme={null}
1730{
1731 "session_id": "abc123",
1732 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1733 "cwd": "/Users/...",
1734 "hook_event_name": "WorktreeRemove",
1735 "worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
1736}
1737```
1738
1739WorktreeRemove 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.
1740
1137### PreCompact1741### PreCompact
1138 1742
1139Runs before Claude Code is about to run a compact operation.1743Runs before Claude Code is about to run a compact operation.
1154 "session_id": "abc123",1758 "session_id": "abc123",
1155 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1759 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1156 "cwd": "/Users/...",1760 "cwd": "/Users/...",
1157 "permission_mode": "default",
1158 "hook_event_name": "PreCompact",1761 "hook_event_name": "PreCompact",
1159 "trigger": "manual",1762 "trigger": "manual",
1160 "custom_instructions": ""1763 "custom_instructions": ""
1161}1764}
1162```1765```
1163 1766
1767### PostCompact
1768
1769Runs 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.
1770
1771The same matcher values apply as for `PreCompact`:
1772
1773| Matcher | When it fires |
1774| :------- | :------------------------------------------------- |
1775| `manual` | After `/compact` |
1776| `auto` | After auto-compact when the context window is full |
1777
1778#### PostCompact input
1779
1780In 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.
1781
1782```json theme={null}
1783{
1784 "session_id": "abc123",
1785 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1786 "cwd": "/Users/...",
1787 "hook_event_name": "PostCompact",
1788 "trigger": "manual",
1789 "compact_summary": "Summary of the compacted conversation..."
1790}
1791```
1792
1793PostCompact hooks have no decision control. They cannot affect the compaction result but can perform follow-up tasks.
1794
1164### SessionEnd1795### SessionEnd
1165 1796
1166Runs when a Claude Code session ends. Useful for cleanup tasks, logging session1797Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
1171| Reason | Description |1802| Reason | Description |
1172| :---------------------------- | :----------------------------------------- |1803| :---------------------------- | :----------------------------------------- |
1173| `clear` | Session cleared with `/clear` command |1804| `clear` | Session cleared with `/clear` command |
1805| `resume` | Session switched via interactive `/resume` |
1174| `logout` | User logged out |1806| `logout` | User logged out |
1175| `prompt_input_exit` | User exited while prompt input was visible |1807| `prompt_input_exit` | User exited while prompt input was visible |
1176| `bypass_permissions_disabled` | Bypass permissions mode was disabled |1808| `bypass_permissions_disabled` | Bypass permissions mode was disabled |
1185 "session_id": "abc123",1817 "session_id": "abc123",
1186 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1818 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1187 "cwd": "/Users/...",1819 "cwd": "/Users/...",
1188 "permission_mode": "default",
1189 "hook_event_name": "SessionEnd",1820 "hook_event_name": "SessionEnd",
1190 "reason": "other"1821 "reason": "other"
1191}1822}
1193 1824
1194SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.1825SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.
1195 1826
1827SessionEnd 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.
1828
1829```bash theme={null}
1830CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude
1831```
1832
1833### Elicitation
1834
1835Runs 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.
1836
1837The matcher field matches against the MCP server name.
1838
1839#### Elicitation input
1840
1841In 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.
1842
1843For form-mode elicitation (the most common case):
1844
1845```json theme={null}
1846{
1847 "session_id": "abc123",
1848 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1849 "cwd": "/Users/...",
1850 "permission_mode": "default",
1851 "hook_event_name": "Elicitation",
1852 "mcp_server_name": "my-mcp-server",
1853 "message": "Please provide your credentials",
1854 "mode": "form",
1855 "requested_schema": {
1856 "type": "object",
1857 "properties": {
1858 "username": { "type": "string", "title": "Username" }
1859 }
1860 }
1861}
1862```
1863
1864For URL-mode elicitation (browser-based authentication):
1865
1866```json theme={null}
1867{
1868 "session_id": "abc123",
1869 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1870 "cwd": "/Users/...",
1871 "permission_mode": "default",
1872 "hook_event_name": "Elicitation",
1873 "mcp_server_name": "my-mcp-server",
1874 "message": "Please authenticate",
1875 "mode": "url",
1876 "url": "https://auth.example.com/login"
1877}
1878```
1879
1880#### Elicitation output
1881
1882To respond programmatically without showing the dialog, return a JSON object with `hookSpecificOutput`:
1883
1884```json theme={null}
1885{
1886 "hookSpecificOutput": {
1887 "hookEventName": "Elicitation",
1888 "action": "accept",
1889 "content": {
1890 "username": "alice"
1891 }
1892 }
1893}
1894```
1895
1896| Field | Values | Description |
1897| :-------- | :---------------------------- | :--------------------------------------------------------------- |
1898| `action` | `accept`, `decline`, `cancel` | Whether to accept, decline, or cancel the request |
1899| `content` | object | Form field values to submit. Only used when `action` is `accept` |
1900
1901Exit code 2 denies the elicitation and shows stderr to the user.
1902
1903### ElicitationResult
1904
1905Runs 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.
1906
1907The matcher field matches against the MCP server name.
1908
1909#### ElicitationResult input
1910
1911In addition to the [common input fields](#common-input-fields), ElicitationResult hooks receive `mcp_server_name`, `action`, and optional `mode`, `elicitation_id`, and `content` fields.
1912
1913```json theme={null}
1914{
1915 "session_id": "abc123",
1916 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1917 "cwd": "/Users/...",
1918 "permission_mode": "default",
1919 "hook_event_name": "ElicitationResult",
1920 "mcp_server_name": "my-mcp-server",
1921 "action": "accept",
1922 "content": { "username": "alice" },
1923 "mode": "form",
1924 "elicitation_id": "elicit-123"
1925}
1926```
1927
1928#### ElicitationResult output
1929
1930To override the user's response, return a JSON object with `hookSpecificOutput`:
1931
1932```json theme={null}
1933{
1934 "hookSpecificOutput": {
1935 "hookEventName": "ElicitationResult",
1936 "action": "decline",
1937 "content": {}
1938 }
1939}
1940```
1941
1942| Field | Values | Description |
1943| :-------- | :---------------------------- | :--------------------------------------------------------------------- |
1944| `action` | `accept`, `decline`, `cancel` | Overrides the user's action |
1945| `content` | object | Overrides form field values. Only meaningful when `action` is `accept` |
1946
1947Exit code 2 blocks the response, changing the effective action to `decline`.
1948
1196## Prompt-based hooks1949## Prompt-based hooks
1197 1950
1198In 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. Prompt-based hooks work with the following events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `UserPromptSubmit`, `Stop`, and `SubagentStop`.1951In 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.
1952
1953Events that support all four hook types (`command`, `http`, `prompt`, and `agent`):
1954
1955* `PermissionRequest`
1956* `PostToolUse`
1957* `PostToolUseFailure`
1958* `PreToolUse`
1959* `Stop`
1960* `SubagentStop`
1961* `TaskCompleted`
1962* `TaskCreated`
1963* `UserPromptSubmit`
1964
1965Events that support `command` and `http` hooks but not `prompt` or `agent`:
1966
1967* `ConfigChange`
1968* `CwdChanged`
1969* `Elicitation`
1970* `ElicitationResult`
1971* `FileChanged`
1972* `InstructionsLoaded`
1973* `Notification`
1974* `PostCompact`
1975* `PreCompact`
1976* `SessionEnd`
1977* `StopFailure`
1978* `SubagentStart`
1979* `TeammateIdle`
1980* `WorktreeCreate`
1981* `WorktreeRemove`
1982
1983`SessionStart` supports only `command` hooks.
1199 1984
1200### How prompt-based hooks work1985### How prompt-based hooks work
1201 1986
1359 2144
1360After 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.2145After 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.
1361 2146
2147Async hook completion notifications are suppressed by default. To see them, enable verbose mode with `Ctrl+O` or start Claude Code with `--verbose`.
2148
1362### Example: run tests after file changes2149### Example: run tests after file changes
1363 2150
1364This 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`:2151This 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`:
1422 2209
1423### Disclaimer2210### Disclaimer
1424 2211
1425Hooks run with your system user's full permissions.2212Command hooks run with your system user's full permissions.
1426 2213
1427<Warning>2214<Warning>
1428 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.2215 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.
1429</Warning>2216</Warning>
1430 2217
1431### Security best practices2218### Security best practices
1438* **Use absolute paths**: specify full paths for scripts, using `"$CLAUDE_PROJECT_DIR"` for the project root2225* **Use absolute paths**: specify full paths for scripts, using `"$CLAUDE_PROJECT_DIR"` for the project root
1439* **Skip sensitive files**: avoid `.env`, `.git/`, keys, etc.2226* **Skip sensitive files**: avoid `.env`, `.git/`, keys, etc.
1440 2227
2228## Windows PowerShell tool
2229
2230On 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).
2231
2232```json theme={null}
2233{
2234 "hooks": {
2235 "PostToolUse": [
2236 {
2237 "matcher": "Write",
2238 "hooks": [
2239 {
2240 "type": "command",
2241 "shell": "powershell",
2242 "command": "Write-Host 'File written'"
2243 }
2244 ]
2245 }
2246 ]
2247 }
2248}
2249```
2250
1441## Debug hooks2251## Debug hooks
1442 2252
1443Run `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.2253Run `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.
1444 2254
1445```2255```text theme={null}
1446[DEBUG] Executing hooks for PostToolUse:Write2256[DEBUG] Executing hooks for PostToolUse:Write
1447[DEBUG] Getting matching hook commands for PostToolUse with query: Write2257[DEBUG] Getting matching hook commands for PostToolUse with query: Write
1448[DEBUG] Found 1 hook matchers in settings2258[DEBUG] Found 1 hook matchers in settings