claude_hooks 1.2.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -0
- data/README.md +21 -15
- data/docs/API/COMMON.md +4 -3
- data/docs/API/DIRECTORY_ADDED.md +34 -0
- data/docs/API/NOTIFICATION.md +5 -4
- data/docs/API/PERMISSION_DENIED.md +4 -0
- data/docs/API/PERMISSION_REQUEST.md +3 -3
- data/docs/API/POST_MODEL_SWITCH.md +53 -0
- data/docs/API/POST_TOOL_USE.md +7 -0
- data/docs/API/POST_TOOL_USE_FAILURE.md +1 -0
- data/docs/API/PRE_MODEL_SWITCH.md +67 -0
- data/docs/API/PRE_TOOL_USE.md +1 -0
- data/docs/API/SESSION_START.md +7 -1
- data/docs/API/SETUP.md +3 -3
- data/docs/API/STOP.md +6 -0
- data/docs/API/SUBAGENT_STOP.md +10 -0
- data/docs/API/TASK_CREATED.md +9 -7
- data/docs/API/USER_PROMPT_SUBMIT.md +14 -0
- data/docs/API/WORKTREE_REMOVE.md +9 -4
- data/docs/external/claude-hooks-reference.md +1188 -345
- data/docs/mitts/prd.md +8 -0
- data/docs/mitts/setup.md +5 -3
- data/lib/claude_hooks/base.rb +12 -0
- data/lib/claude_hooks/directory_added.rb +24 -0
- data/lib/claude_hooks/output/base.rb +6 -0
- data/lib/claude_hooks/output/directory_added.rb +21 -0
- data/lib/claude_hooks/output/notification.rb +10 -1
- data/lib/claude_hooks/output/post_model_switch.rb +50 -0
- data/lib/claude_hooks/output/post_tool_batch.rb +10 -0
- data/lib/claude_hooks/output/post_tool_use.rb +23 -5
- data/lib/claude_hooks/output/pre_model_switch.rb +121 -0
- data/lib/claude_hooks/output/pre_tool_use.rb +8 -0
- data/lib/claude_hooks/output/task_created.rb +34 -1
- data/lib/claude_hooks/output/user_prompt_expansion.rb +10 -0
- data/lib/claude_hooks/output/user_prompt_submit.rb +19 -6
- data/lib/claude_hooks/output/worktree_remove.rb +4 -2
- data/lib/claude_hooks/permission_denied.rb +7 -0
- data/lib/claude_hooks/permission_request.rb +10 -1
- data/lib/claude_hooks/post_model_switch.rb +70 -0
- data/lib/claude_hooks/post_tool_use.rb +14 -0
- data/lib/claude_hooks/post_tool_use_failure.rb +7 -0
- data/lib/claude_hooks/pre_model_switch.rb +90 -0
- data/lib/claude_hooks/pre_tool_use.rb +7 -0
- data/lib/claude_hooks/session_start.rb +18 -0
- data/lib/claude_hooks/setup.rb +3 -0
- data/lib/claude_hooks/task_created.rb +17 -0
- data/lib/claude_hooks/user_prompt_submit.rb +26 -5
- data/lib/claude_hooks/version.rb +1 -1
- data/lib/claude_hooks.rb +6 -0
- metadata +11 -2
|
@@ -8,17 +8,17 @@
|
|
|
8
8
|
|
|
9
9
|
For a quickstart guide with examples, see [Automate actions with hooks](https://code.claude.com/docs/en/hooks-guide).
|
|
10
10
|
|
|
11
|
-
Hooks are user-defined shell commands, HTTP endpoints,
|
|
11
|
+
Hooks are user-defined shell commands, HTTP endpoints, MCP tool calls, LLM prompts, or subagents that execute automatically at specific points in Claude Code’s lifecycle. Claude Code fires the same hook events wherever it runs: sessions in the terminal, IDE extensions, the [Desktop app](https://code.claude.com/docs/en/desktop-quickstart), and [cloud sessions](https://code.claude.com/docs/en/claude-code-on-the-web). 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.A plugin can also register hooks as JavaScript functions that Claude Code calls in its own process, which can draw in the interface as well as act on events. A plugin that does is a [mod](https://code.claude.com/docs/en/plugins/mods/overview), and those function hooks are covered in [React to events](https://code.claude.com/docs/en/plugins/mods/events) rather than here. The hooks on this page keep working alongside mods.
|
|
12
12
|
|
|
13
13
|
## [](https://code.claude.com/docs/en/hooks\#hook-lifecycle) Hook lifecycle
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Claude Code runs hooks at specific points during a 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.Events fall into three cadences:
|
|
16
16
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
- on every tool call inside the agentic loop: `PreToolUse` and `PostToolUse`
|
|
17
|
+
- per session: `SessionStart` and `SessionEnd`
|
|
18
|
+
- per turn: `UserPromptSubmit`, `Stop`, and `StopFailure`
|
|
19
|
+
- on every tool call inside the agentic loop: `PreToolUse` and `PostToolUse`, except [`EndConversation`](https://code.claude.com/docs/en/tools-reference#endconversation-tool-behavior) calls, which skip both
|
|
20
20
|
|
|
21
|
-
, and Stop or StopFailure, followed by TeammateIdle, PreCompact, PostCompact, and SessionEnd, with Elicitation and ElicitationResult nested inside MCP tool execution, PermissionDenied as a side branch from PermissionRequest for auto-mode denials, WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, FileChanged, and DirectoryAdded as standalone async events, PreModelSwitch as a standalone sequential event that runs before a requested model switch, PostModelSwitch as a standalone async event that runs after the session's model changes, and MessageDisplay as a display-only event that runs while assistant message text streams](https://mintcdn.com/claude-code/x7pO8l4XcvAXCoVc/images/hooks-lifecycle.svg?fit=max&auto=format&n=x7pO8l4XcvAXCoVc&q=85&s=81b9256c1bbe8832553485f5d9e9c746)
|
|
22
22
|
|
|
23
23
|
The table below summarizes when each event fires. The [Hook events](https://code.claude.com/docs/en/hooks#hook-events) section documents the full input schema and decision control options for each one.
|
|
24
24
|
|
|
@@ -26,11 +26,11 @@ The table below summarizes when each event fires. The [Hook events](https://code
|
|
|
26
26
|
| --- | --- |
|
|
27
27
|
| `SessionStart` | When a session begins or resumes |
|
|
28
28
|
| `Setup` | When you start Claude Code with `--init-only`, or with `--init` or `--maintenance` in `-p` mode. For one-time preparation in CI or scripts |
|
|
29
|
-
| `UserPromptSubmit` | When
|
|
29
|
+
| `UserPromptSubmit` | When a prompt is submitted, before Claude processes it. Also fires on [turns Claude Code starts on its own](https://code.claude.com/docs/en/hooks#userpromptsubmit) |
|
|
30
30
|
| `UserPromptExpansion` | When a user-typed command expands into a prompt, before it reaches Claude. Can block the expansion |
|
|
31
31
|
| `PreToolUse` | Before a tool call executes. Can block it |
|
|
32
|
-
| `PermissionRequest` | When a permission
|
|
33
|
-
| `PermissionDenied` | When a tool call
|
|
32
|
+
| `PermissionRequest` | When a tool call needs a permission decision |
|
|
33
|
+
| `PermissionDenied` | When auto mode denies a tool call, including denials without a classifier verdict. Use JSON `hookSpecificOutput.retry: true` to tell the model it may retry the denied tool call. Claude Code ignores `retry` when the classifier produced no verdict |
|
|
34
34
|
| `PostToolUse` | After a tool call succeeds |
|
|
35
35
|
| `PostToolUseFailure` | After a tool call fails |
|
|
36
36
|
| `PostToolBatch` | After a full batch of parallel tool calls resolves, before the next model call |
|
|
@@ -41,23 +41,33 @@ The table below summarizes when each event fires. The [Hook events](https://code
|
|
|
41
41
|
| `TaskCreated` | When a task is being created via `TaskCreate` |
|
|
42
42
|
| `TaskCompleted` | When a task is being marked as completed |
|
|
43
43
|
| `Stop` | When Claude finishes responding |
|
|
44
|
-
| `StopFailure` | When the turn ends due to an API error
|
|
44
|
+
| `StopFailure` | When the turn ends due to an API error |
|
|
45
45
|
| `TeammateIdle` | When an [agent team](https://code.claude.com/docs/en/agent-teams) teammate is about to go idle |
|
|
46
46
|
| `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 |
|
|
47
47
|
| `ConfigChange` | When a configuration file changes during a session |
|
|
48
48
|
| `CwdChanged` | When the working directory changes, for example when Claude executes a `cd` command. Useful for reactive environment management with tools like direnv |
|
|
49
|
+
| `DirectoryAdded` | When a working directory is added mid-session via `/add-dir` or the SDK `register_repo_root` control request |
|
|
49
50
|
| `FileChanged` | When a watched file changes on disk. The `matcher` field specifies which filenames to watch |
|
|
50
|
-
| `WorktreeCreate` | When a worktree is being created via `--worktree
|
|
51
|
-
| `WorktreeRemove` | When a worktree
|
|
51
|
+
| `WorktreeCreate` | When a worktree is being created via `--worktree`, `isolation: "worktree"`, or for a background session. Replaces default git behavior |
|
|
52
|
+
| `WorktreeRemove` | When a worktree that a `WorktreeCreate` hook created is being removed |
|
|
52
53
|
| `PreCompact` | Before context compaction |
|
|
53
54
|
| `PostCompact` | After context compaction completes |
|
|
55
|
+
| `PreModelSwitch` | Before Claude Code applies a model switch that you or a client requested. Can block the switch |
|
|
56
|
+
| `PostModelSwitch` | After the session’s model changes, including changes Claude Code makes on its own, such as restoring the model when you resume a session |
|
|
54
57
|
| `Elicitation` | When an MCP server requests user input during a tool call |
|
|
55
58
|
| `ElicitationResult` | After a user responds to an MCP elicitation, before the response is sent back to the server |
|
|
56
59
|
| `SessionEnd` | When a session terminates |
|
|
57
60
|
|
|
58
61
|
### [](https://code.claude.com/docs/en/hooks\#how-a-hook-resolves) How a hook resolves
|
|
59
62
|
|
|
60
|
-
To see how
|
|
63
|
+
To see how the event, the matcher, and the handler fit together, consider this `PreToolUse` hook that blocks destructive shell commands.
|
|
64
|
+
|
|
65
|
+
- macOS/Linux
|
|
66
|
+
|
|
67
|
+
- Windows (PowerShell)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
The `matcher` narrows to Bash tool calls and the `if` condition narrows further to Bash subcommands matching `rm *`, so `block-rm.sh` only spawns when both filters match:
|
|
61
71
|
|
|
62
72
|
```
|
|
63
73
|
{
|
|
@@ -79,7 +89,7 @@ To see how these pieces fit together, consider this `PreToolUse` hook that block
|
|
|
79
89
|
}
|
|
80
90
|
```
|
|
81
91
|
|
|
82
|
-
The script reads the JSON input from stdin, extracts the command, and returns a `permissionDecision` of `"deny"` if it contains `rm -rf
|
|
92
|
+
The script reads the JSON input from stdin, extracts the command, and returns a `permissionDecision` of `"deny"` if it contains `rm -rf`. Save it to `.claude/hooks/block-rm.sh` in your project and make it executable with `chmod +x .claude/hooks/block-rm.sh` so Claude Code can run it:
|
|
83
93
|
|
|
84
94
|
```
|
|
85
95
|
#!/bin/bash
|
|
@@ -99,13 +109,73 @@ else
|
|
|
99
109
|
fi
|
|
100
110
|
```
|
|
101
111
|
|
|
102
|
-
|
|
112
|
+
This script, like the other Bash examples on this page that parse JSON input, uses `jq`, so install `jq` and make sure it is on your `PATH` before trying them.
|
|
103
113
|
|
|
104
|
-
|
|
114
|
+
The matcher `Bash|PowerShell` covers the [PowerShell tool](https://code.claude.com/docs/en/hooks#powershell) as well as Bash. A single `if` rule matches only one tool’s calls, so each tool gets its own handler: the first narrows to Bash subcommands matching `rm *`, the second to PowerShell commands matching `Remove-Item *`. Both run the same script through `powershell.exe`:
|
|
105
115
|
|
|
106
|
-
|
|
116
|
+
```
|
|
117
|
+
{
|
|
118
|
+
"hooks": {
|
|
119
|
+
"PreToolUse": [\
|
|
120
|
+
{\
|
|
121
|
+
"matcher": "Bash|PowerShell",\
|
|
122
|
+
"hooks": [\
|
|
123
|
+
{\
|
|
124
|
+
"type": "command",\
|
|
125
|
+
"if": "Bash(rm *)",\
|
|
126
|
+
"command": "powershell.exe",\
|
|
127
|
+
"args": [\
|
|
128
|
+
"-NoProfile",\
|
|
129
|
+
"-ExecutionPolicy",\
|
|
130
|
+
"Bypass",\
|
|
131
|
+
"-File",\
|
|
132
|
+
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"\
|
|
133
|
+
]\
|
|
134
|
+
},\
|
|
135
|
+
{\
|
|
136
|
+
"type": "command",\
|
|
137
|
+
"if": "PowerShell(Remove-Item *)",\
|
|
138
|
+
"command": "powershell.exe",\
|
|
139
|
+
"args": [\
|
|
140
|
+
"-NoProfile",\
|
|
141
|
+
"-ExecutionPolicy",\
|
|
142
|
+
"Bypass",\
|
|
143
|
+
"-File",\
|
|
144
|
+
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"\
|
|
145
|
+
]\
|
|
146
|
+
}\
|
|
147
|
+
]\
|
|
148
|
+
}\
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The `-NoProfile` flag skips loading your PowerShell profile so the hook starts fast, and `-ExecutionPolicy Bypass` lets PowerShell run the local script file.The script reads the JSON input from stdin, extracts the command, and returns a `permissionDecision` of `"deny"` if it contains `rm -rf` or `Remove-Item` followed by `-Recurse`. Save it to `.claude/hooks/block-rm.ps1` in your project:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
# .claude/hooks/block-rm.ps1
|
|
158
|
+
$callInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
159
|
+
$command = $callInput.tool_input.command
|
|
160
|
+
|
|
161
|
+
if ($command -match 'rm -rf|Remove-Item.*-Recurse') {
|
|
162
|
+
@{
|
|
163
|
+
hookSpecificOutput = @{
|
|
164
|
+
hookEventName = "PreToolUse"
|
|
165
|
+
permissionDecision = "deny"
|
|
166
|
+
permissionDecisionReason = "Destructive command blocked by hook"
|
|
167
|
+
}
|
|
168
|
+
} | ConvertTo-Json
|
|
169
|
+
} else {
|
|
170
|
+
exit 0 # no decision; normal permission flow applies
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"` against the macOS/Linux config. Here’s what happens:
|
|
107
175
|
|
|
108
|
-
[
|
|
176
|
+

|
|
177
|
+
|
|
178
|
+
1
|
|
109
179
|
|
|
110
180
|
Event fires
|
|
111
181
|
|
|
@@ -117,24 +187,18 @@ The `PreToolUse` event fires. Claude Code sends the tool input as JSON on stdin
|
|
|
117
187
|
|
|
118
188
|
2
|
|
119
189
|
|
|
120
|
-
[Navigate to header](https://code.claude.com/docs/en/hooks#)
|
|
121
|
-
|
|
122
190
|
Matcher checks
|
|
123
191
|
|
|
124
192
|
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.
|
|
125
193
|
|
|
126
194
|
3
|
|
127
195
|
|
|
128
|
-
[Navigate to header](https://code.claude.com/docs/en/hooks#)
|
|
129
|
-
|
|
130
196
|
If condition checks
|
|
131
197
|
|
|
132
198
|
The `if` condition `"Bash(rm *)"` matches because `rm -rf /tmp/build` is a subcommand matching `rm *`, so this handler spawns. If the command had been `npm test`, the `if` check would fail and `block-rm.sh` would never run, avoiding the process spawn overhead. The `if` field is optional; without it, every handler in the matched group runs.
|
|
133
199
|
|
|
134
200
|
4
|
|
135
201
|
|
|
136
|
-
[Navigate to header](https://code.claude.com/docs/en/hooks#)
|
|
137
|
-
|
|
138
202
|
Hook handler runs
|
|
139
203
|
|
|
140
204
|
The script inspects the full command and finds `rm -rf`, so it prints a decision to stdout:
|
|
@@ -153,8 +217,6 @@ If the command had been a safer `rm` variant like `rm file.txt`, the script woul
|
|
|
153
217
|
|
|
154
218
|
5
|
|
155
219
|
|
|
156
|
-
[Navigate to header](https://code.claude.com/docs/en/hooks#)
|
|
157
|
-
|
|
158
220
|
Claude Code acts on the result
|
|
159
221
|
|
|
160
222
|
Claude Code reads the JSON decision, blocks the tool call, and shows Claude the reason.
|
|
@@ -181,12 +243,23 @@ Where you define a hook determines its scope:
|
|
|
181
243
|
| --- | --- | --- |
|
|
182
244
|
| `~/.claude/settings.json` | All your projects | No, local to your machine |
|
|
183
245
|
| `.claude/settings.json` | Single project | Yes, can be committed to the repo |
|
|
184
|
-
| `.claude/settings.local.json` | Single project | No, gitignored when Claude Code
|
|
246
|
+
| `.claude/settings.local.json` | Single project | No, gitignored when Claude Code saves a setting to it |
|
|
185
247
|
| Managed policy settings | Organization-wide | Yes, admin-controlled |
|
|
186
|
-
| [Plugin](https://code.claude.com/docs/en/plugins)`hooks/hooks.json` | When plugin is enabled | Yes, bundled with the plugin |
|
|
187
|
-
| [Skill](https://code.claude.com/docs/en/skills)
|
|
248
|
+
| [Plugin](https://code.claude.com/docs/en/plugins/overview)`hooks/hooks.json` | When plugin is enabled | Yes, bundled with the plugin |
|
|
249
|
+
| [Skill](https://code.claude.com/docs/en/skills) frontmatter | The rest of the session once the skill is invoked. See [Hooks in skills and agents](https://code.claude.com/docs/en/hooks#hooks-in-skills-and-agents) | Yes, defined in the skill file |
|
|
250
|
+
| [Subagent](https://code.claude.com/docs/en/sub-agents) frontmatter | While that subagent is running | Yes, defined in the subagent file |
|
|
251
|
+
|
|
252
|
+
[Cloud sessions](https://code.claude.com/docs/en/claude-code-on-the-web) don’t read your local `~/.claude/settings.json`. In a [self-hosted environment](https://code.claude.com/docs/en/self-hosted-environments-configuration#permissions-and-tool-approval), Claude Code also runs the hooks the operator seeded from the runner host’s `~/.claude/`, and it runs the hooks in the runner image’s managed settings file when that file is among the [managed sources Claude Code applies](https://code.claude.com/docs/en/managed-settings#how-claude-code-combines-managed-sources), which by default means only when neither server-managed settings nor an MDM-delivered Claude Code policy supplies the managed tier. See [what carries over from your setup](https://code.claude.com/docs/en/cloud-environments#what-carries-over-from-your-setup) for which settings files and plugins, and so which hooks, reach a cloud session.For details on settings file resolution, see [settings](https://code.claude.com/docs/en/settings).Hooks from settings files, managed policy settings, and plugins also run inside [subagents](https://code.claude.com/docs/en/sub-agents). When a subagent calls a tool, tool events such as `PreToolUse` and `PostToolUse` fire the same configured hooks as in the main conversation, and the input carries the `agent_id` and `agent_type` [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields) that identify the subagent.Administrators can use [`allowManagedHooksOnly`](https://code.claude.com/docs/en/settings-reference#allowmanagedhooksonly) in [managed settings](https://code.claude.com/docs/en/managed-settings) to restrict which hooks run:
|
|
253
|
+
|
|
254
|
+
- Your user, project, local, and plugin hooks are blocked. Hooks from plugins force-enabled in managed settings `enabledPlugins` are exempt
|
|
255
|
+
- Claude Code also narrows your [`statusLine`](https://code.claude.com/docs/en/statusline), [`fileSuggestion`](https://code.claude.com/docs/en/settings-reference#filesuggestion), and [`subagentStatusLine`](https://code.claude.com/docs/en/statusline#subagent-status-lines) settings to managed settings
|
|
256
|
+
- Claude Code also disables plugins with a [`command` source](https://code.claude.com/docs/en/plugins/marketplace-reference#command-plugin-source), including plugins force-enabled in managed settings `enabledPlugins`, unless [`disableCommandPluginSources`](https://code.claude.com/docs/en/settings-reference#disablecommandpluginsources) is explicitly set to `false`. `command` sources require Claude Code v2.1.229 or later
|
|
257
|
+
- Claude Code also blocks marketplace [`headersHelper` commands](https://code.claude.com/docs/en/plugins/host-marketplace#authenticate-archive-downloads) unless [`disableCommandPluginSources`](https://code.claude.com/docs/en/settings-reference#disablecommandpluginsources) is explicitly set to `false`, except for a marketplace that managed settings themselves declare
|
|
188
258
|
|
|
189
|
-
|
|
259
|
+
See [what runs under `allowManagedHooksOnly`](https://code.claude.com/docs/en/settings-reference#what-runs-under-allowmanagedhooksonly).Hook entries merge across settings levels rather than replacing each other: user, project, and local settings add their own hooks without removing managed ones, and the [`disableAllHooks`](https://code.claude.com/docs/en/hooks#disable-or-remove-hooks) setting can’t disable managed hooks from outside managed settings.The [HTTP hook allowlists](https://code.claude.com/docs/en/settings-reference#hook-and-skill-settings) apply to hooks from every source, including managed policy settings:
|
|
260
|
+
|
|
261
|
+
- `allowedHttpHookUrls`: when defined at any settings level, Claude Code runs an HTTP hook handler only if its URL matches the merged allowlist
|
|
262
|
+
- `httpHookAllowedEnvVars`: when defined, Claude Code interpolates only the environment variables on that list into hook headers
|
|
190
263
|
|
|
191
264
|
### [](https://code.claude.com/docs/en/hooks\#matcher-patterns) Matcher patterns
|
|
192
265
|
|
|
@@ -198,29 +271,31 @@ The `matcher` field filters when hooks fire. How a matcher is evaluated depends
|
|
|
198
271
|
| Only letters, digits, `_`, `-`, spaces, `,`, and `|` | Exact string, or list of exact strings separated by `|` or `,` with optional surrounding whitespace | `Bash` matches only the Bash tool; `Edit|Write` and `Edit, Write` each match either tool exactly; `code-reviewer` matches only that agent type |
|
|
199
272
|
| Contains any other character | JavaScript regular expression, unanchored | `^Notebook` matches any tool whose name starts with `Notebook`; `mcp__memory__.*` matches every tool from the `memory` server |
|
|
200
273
|
|
|
201
|
-
A matcher on the regular-expression path is tested with JavaScript’s `RegExp.prototype.test`, which succeeds on a match anywhere in the value. `Edit.*` matches both `Edit` and `NotebookEdit`; wrap the pattern in `^` and `$`, as in `^Edit$`, when you need a whole-string match
|
|
274
|
+
A matcher on the regular-expression path is tested with JavaScript’s `RegExp.prototype.test`, which succeeds on a match anywhere in the value. `Edit.*` matches both `Edit` and `NotebookEdit`; wrap the pattern in `^` and `$`, as in `^Edit$`, when you need a whole-string match.`FileChanged` and `StopFailure` use a narrower exact-match set of letters, digits, `_`, and `|` only. A hyphen, space, or comma in a matcher for those two events keeps it on the regular-expression path, and only `|` separates alternatives. Every other event with matcher support in the table that follows accepts `|` or `,`.The `FileChanged` event doesn’t follow these rules when building its watch list. See [FileChanged](https://code.claude.com/docs/en/hooks#filechanged).Each event type matches on a different field:
|
|
202
275
|
|
|
203
276
|
| Event | What the matcher filters | Example matcher values |
|
|
204
277
|
| --- | --- | --- |
|
|
205
278
|
| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied` | tool name | `Bash`, `Edit|Write`, `mcp__.*` |
|
|
206
|
-
| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |
|
|
279
|
+
| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact`, `fork` |
|
|
207
280
|
| `Setup` | which CLI flag triggered setup | `init`, `maintenance` |
|
|
208
|
-
| `SessionEnd` | why the session ended | `clear`, `resume`, `logout`, `prompt_input_exit`, `
|
|
209
|
-
| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`, `elicitation_complete`, `elicitation_response`, `agent_needs_input`, `agent_completed` |
|
|
281
|
+
| `SessionEnd` | why the session ended | `clear`, `resume`, `logout`, `prompt_input_exit`, `other` |
|
|
282
|
+
| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`, `elicitation_url_dialog`, `elicitation_complete`, `elicitation_response`, `agent_needs_input`, `agent_completed`, `quota_auto_resume_fired`, `quota_auto_resume_stale`, `quota_auto_resume_disabled` |
|
|
210
283
|
| `SubagentStart` | agent type | `general-purpose`, `Explore`, `Plan`, custom agent names, or plugin-scoped names like `^my-plugin:reviewer$` |
|
|
211
284
|
| `PreCompact`, `PostCompact` | what triggered compaction | `manual`, `auto` |
|
|
285
|
+
| `PreModelSwitch`, `PostModelSwitch` | canonical name of the model the session switches to, as described under [PreModelSwitch](https://code.claude.com/docs/en/hooks#premodelswitch) | `claude-opus-5`, `claude-opus-4-6|claude-opus-5`, `.*opus.*` |
|
|
212
286
|
| `SubagentStop` | agent type | same values as `SubagentStart` |
|
|
213
287
|
| `ConfigChange` | configuration source | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` |
|
|
214
|
-
| `CwdChanged` | no matcher support | always fires on every
|
|
288
|
+
| `CwdChanged` | no matcher support | always fires on every occurrence |
|
|
289
|
+
| `DirectoryAdded` | how the directory was added | `slash_command`, `register_repo_root` |
|
|
215
290
|
| `FileChanged` | literal filenames to watch (see [FileChanged](https://code.claude.com/docs/en/hooks#filechanged)) | `.envrc|.env` |
|
|
216
|
-
| `StopFailure` | error type | `rate_limit`, `overloaded`, `authentication_failed`, `oauth_org_not_allowed`, `billing_error`, `invalid_request`, `model_not_found`, `server_error`, `max_output_tokens`, `unknown` |
|
|
291
|
+
| `StopFailure` | error type | `rate_limit`, `overloaded`, `authentication_failed`, `oauth_org_not_allowed`, `account_on_hold`, `billing_error`, `invalid_request`, `model_not_found`, `server_error`, `max_output_tokens`, `cloud_credential_error`, `unknown` |
|
|
217
292
|
| `InstructionsLoaded` | load reason | `session_start`, `nested_traversal`, `path_glob_match`, `include`, `compact` |
|
|
218
293
|
| `UserPromptExpansion` | command name | your skill or command names |
|
|
219
294
|
| `Elicitation` | MCP server name | your configured MCP server names |
|
|
220
295
|
| `ElicitationResult` | MCP server name | same values as `Elicitation` |
|
|
221
296
|
| `UserPromptSubmit`, `PostToolBatch`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove`, `MessageDisplay` | no matcher support | always fires on every occurrence |
|
|
222
297
|
|
|
223
|
-
|
|
298
|
+
Matching `StopFailure` on `cloud_credential_error` requires Claude Code v2.1.267 or later, the first version that reports credential-load failures under that value rather than `server_error` or `unknown`.For most events, Claude Code evaluates the matcher against a field from the [JSON input](https://code.claude.com/docs/en/hooks#hook-input-and-output) it sends to your hook on stdin. For tool events, that field is `tool_name`. For `PreModelSwitch` and `PostModelSwitch`, Claude Code evaluates the matcher against the canonical name it derives from `to_model`, as described under [PreModelSwitch](https://code.claude.com/docs/en/hooks#premodelswitch). Each [hook event](https://code.claude.com/docs/en/hooks#hook-events) section lists the full set of matcher values and the input schema for that event.This example runs a linting script only when Claude writes or edits a file:
|
|
224
299
|
|
|
225
300
|
```
|
|
226
301
|
{
|
|
@@ -240,7 +315,7 @@ The matcher runs against a field from the [JSON input](https://code.claude.com/d
|
|
|
240
315
|
}
|
|
241
316
|
```
|
|
242
317
|
|
|
243
|
-
|
|
318
|
+
If you add a `matcher` field to an event without matcher support, it is silently ignored.For tool events, you can filter more narrowly by setting the [`if` field](https://code.claude.com/docs/en/hooks#common-fields) on individual hook handlers. `if` uses [permission rule syntax](https://code.claude.com/docs/en/permissions) to match against the tool name and arguments together, so `"Bash(git *)"` runs when any subcommand of the Bash input matches `git *` and `"Edit(*.ts)"` runs only for TypeScript files.
|
|
244
319
|
|
|
245
320
|
#### [](https://code.claude.com/docs/en/hooks\#match-mcp-tools) Match MCP tools
|
|
246
321
|
|
|
@@ -256,7 +331,7 @@ To match every tool from a server, append `.*` to the server prefix. The `.*` is
|
|
|
256
331
|
- `mcp__brave-search__.*` matches all tools from a server whose name contains a hyphen
|
|
257
332
|
- `mcp__.*__write.*` matches any tool whose name starts with `write` from any server
|
|
258
333
|
|
|
259
|
-
|
|
334
|
+
Tools from a [plugin-bundled MCP server](https://code.claude.com/docs/en/mcp#plugin-provided-mcp-servers) use a scoped server segment that includes the plugin name: `mcp__plugin_<plugin-name>_<server-name>__<tool>`. A matcher written against the bare server key never fires for these tools. For a plugin named `my-plugin` that bundles a server under the key `db`, a `query` tool appears as `mcp__plugin_my-plugin_db__query`, so the matcher for every tool from that server is `mcp__plugin_my-plugin_db__.*`. Use the same scoped tool name in a handler’s [`if` field](https://code.claude.com/docs/en/hooks#common-fields). See [Plugin-provided MCP servers](https://code.claude.com/docs/en/mcp#plugin-provided-mcp-servers) for how the scoped name is built.This example logs all memory server operations and validates write operations from any MCP server:
|
|
260
335
|
|
|
261
336
|
```
|
|
262
337
|
{
|
|
@@ -291,11 +366,11 @@ Each object in the inner `hooks` array is a hook handler: the shell command, HTT
|
|
|
291
366
|
|
|
292
367
|
- **[Command hooks](https://code.claude.com/docs/en/hooks#command-hook-fields)** (`type: "command"`): run a shell command. Your script receives the event’s [JSON input](https://code.claude.com/docs/en/hooks#hook-input-and-output) on stdin and communicates results back through exit codes and stdout.
|
|
293
368
|
- **[HTTP hooks](https://code.claude.com/docs/en/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](https://code.claude.com/docs/en/hooks#json-output) as command hooks.
|
|
294
|
-
- **[MCP tool hooks](https://code.claude.com/docs/en/hooks#mcp-tool-hook-fields)** (`type: "mcp_tool"`): call a tool on
|
|
295
|
-
- **[Prompt hooks](https://code.claude.com/docs/en/hooks#prompt-and-agent-hook-fields)** (`type: "prompt"`): send a prompt to a Claude model for single-turn evaluation. The model returns
|
|
369
|
+
- **[MCP tool hooks](https://code.claude.com/docs/en/hooks#mcp-tool-hook-fields)** (`type: "mcp_tool"`): call a tool on a configured [MCP server](https://code.claude.com/docs/en/mcp). The tool’s text output is treated like command-hook stdout.
|
|
370
|
+
- **[Prompt hooks](https://code.claude.com/docs/en/hooks#prompt-and-agent-hook-fields)** (`type: "prompt"`): send a prompt to a Claude model for single-turn evaluation. The model returns its decision as JSON. See [Prompt-based hooks](https://code.claude.com/docs/en/hooks#prompt-based-hooks).
|
|
296
371
|
- **[Agent hooks](https://code.claude.com/docs/en/hooks#prompt-and-agent-hook-fields)** (`type: "agent"`): spawn a subagent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. Agent hooks are experimental and may change. See [Agent-based hooks](https://code.claude.com/docs/en/hooks#agent-based-hooks).
|
|
297
372
|
|
|
298
|
-
All matching hooks run in parallel
|
|
373
|
+
All matching hooks run in parallel. If you define the same handler in more than one settings file, it runs once. A plugin’s or skill’s copy of the same handler stays separate.Handlers run in the current directory with Claude Code’s environment. If the current directory no longer exists, for example a worktree or temp directory that another shell deleted mid-session, Claude Code runs command hooks from the first of these that still exists: the directory the session started in, the project root, your home directory, or the system temp directory. Claude Code records a warning naming the fallback directory in the [debug log](https://code.claude.com/docs/en/hooks#debug-hooks). For a worktree session that you start from the desktop app, see [What worktrees share with the main checkout](https://code.claude.com/docs/en/worktrees#what-worktrees-share-with-the-main-checkout).The `$CLAUDE_CODE_REMOTE` environment variable is `"true"` in remote web environments and not set in the local CLI. Claude Code v2.1.199 and later sets [`$CLAUDE_CODE_BRIDGE_SESSION_ID`](https://code.claude.com/docs/en/env-vars) to the [Remote Control](https://code.claude.com/docs/en/remote-control) session ID while the local session has an active Remote Control connection.
|
|
299
374
|
|
|
300
375
|
#### [](https://code.claude.com/docs/en/hooks\#common-fields) Common fields
|
|
301
376
|
|
|
@@ -304,12 +379,16 @@ These fields apply to all hook types:
|
|
|
304
379
|
| Field | Required | Description |
|
|
305
380
|
| --- | --- | --- |
|
|
306
381
|
| `type` | yes | `"command"`, `"http"`, `"mcp_tool"`, `"prompt"`, or `"agent"` |
|
|
307
|
-
| `if` | no | Permission rule syntax to filter when this hook runs, such as `"Bash(git *)"` or `"Edit(*.ts)"`. The hook command only runs if the tool call matches the pattern. See the [Bash matching table](https://code.claude.com/docs/en/hooks#bash-if-matching)
|
|
308
|
-
| `timeout` | no | Seconds before canceling. Defaults: 600 for `command`, `http`, and `mcp_tool`; 30 for `prompt`; 60 for `agent`. [`UserPromptSubmit`](https://code.claude.com/docs/en/hooks#userpromptsubmit)
|
|
382
|
+
| `if` | no | [Permission rule syntax](https://code.claude.com/docs/en/permissions#permission-rule-syntax) to filter when this hook runs, such as `"Bash(git *)"` or `"Edit(*.ts)"`. The hook command only runs if the tool call matches the pattern. See the [Bash matching table](https://code.claude.com/docs/en/hooks#bash-if-matching) for how Bash patterns evaluate against subcommands, `$()`, and backticks. Only evaluated on tool events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, and `PermissionDenied`. On other events, a hook with `if` set never runs |
|
|
383
|
+
| `timeout` | no | Seconds before canceling. Claude Code doesn’t enforce it on a command hook you run with [`async: true`](https://code.claude.com/docs/en/hooks#run-hooks-in-the-background). Defaults: 600 for `command`, `http`, and `mcp_tool`; 30 for `prompt`; 60 for `agent`. Claude Code lowers the `command`, `http`, and `mcp_tool` default to 30 on [`UserPromptSubmit`](https://code.claude.com/docs/en/hooks#userpromptsubmit), [`PreModelSwitch`](https://code.claude.com/docs/en/hooks#premodelswitch), and [`PostModelSwitch`](https://code.claude.com/docs/en/hooks#postmodelswitch), and to 10 on [`MessageDisplay`](https://code.claude.com/docs/en/hooks#messagedisplay). [`SessionEnd`](https://code.claude.com/docs/en/hooks#sessionend) hooks share a 1.5-second budget; if your settings set a longer per-hook `timeout`, Claude Code raises the budget to match, up to 60 seconds |
|
|
309
384
|
| `statusMessage` | no | Custom spinner message displayed while the hook runs |
|
|
310
|
-
| `once` | no | If `true`, runs
|
|
385
|
+
| `once` | no | If `true`, Claude Code removes the hook after its first successful run. A run that fails, blocks with exit code 2, or times out leaves the hook in place, so it runs again on the next matching event. Only honored for hooks declared in [skill frontmatter](https://code.claude.com/docs/en/hooks#hooks-in-skills-and-agents); ignored in settings files and agent frontmatter |
|
|
386
|
+
|
|
387
|
+
The `if` field holds exactly one permission rule. There is no `&&`, `||`, or list syntax for combining rules; to apply multiple conditions, define a separate hook handler for each.In an `if` condition for a file tool, a single-segment directory pattern like `"Edit(src/**)"` matches only the `src` directory in the working directory and the files under it. To match a directory named `src` at any depth, write `"Edit(**/src/**)"`. Before v2.1.214, `"Edit(src/**)"` matched a directory named `src` at any depth under the working directory.
|
|
311
388
|
|
|
312
|
-
|
|
389
|
+
#### [](https://code.claude.com/docs/en/hooks\#bash-if-matching) How `if` patterns match Bash commands
|
|
390
|
+
|
|
391
|
+
For Bash patterns in the [`if` field](https://code.claude.com/docs/en/hooks#common-fields), whether your hook command runs depends on the shape of the pattern and the Bash command Claude is invoking. Leading `VAR=value` assignments are stripped before matching.
|
|
313
392
|
|
|
314
393
|
| `if` pattern | Bash command | Hook runs? | Why |
|
|
315
394
|
| --- | --- | --- | --- |
|
|
@@ -319,7 +398,7 @@ The `if` field holds exactly one permission rule. There is no `&&`, `||`, or lis
|
|
|
319
398
|
| `Bash(rm *)` | `echo $(date)` | no | no subcommand matches `rm *` |
|
|
320
399
|
| `Bash(git push *)` | `echo $(date)` | yes | patterns that specify more than the command name run the hook anyway on `$()`, backticks, or `$VAR` |
|
|
321
400
|
|
|
322
|
-
|
|
401
|
+
When Claude Code can’t determine which commands the Bash input runs, it runs your hook regardless of the pattern. Because the `if` filter is best-effort, use the [permission system](https://code.claude.com/docs/en/permissions) rather than a hook to enforce a hard allow or deny.
|
|
323
402
|
|
|
324
403
|
#### [](https://code.claude.com/docs/en/hooks\#command-hook-fields) Command hook fields
|
|
325
404
|
|
|
@@ -330,8 +409,9 @@ In addition to the [common fields](https://code.claude.com/docs/en/hooks#common-
|
|
|
330
409
|
| `command` | yes | Shell command to execute. With `args`, the executable to spawn directly. See [Exec form and shell form](https://code.claude.com/docs/en/hooks#exec-form-and-shell-form) |
|
|
331
410
|
| `args` | no | Argument list. When present, `command` is resolved as an executable and spawned directly with `args` as the argument vector, with no shell involved. See [Exec form and shell form](https://code.claude.com/docs/en/hooks#exec-form-and-shell-form) |
|
|
332
411
|
| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](https://code.claude.com/docs/en/hooks#run-hooks-in-the-background) |
|
|
333
|
-
| `asyncRewake` | no | If `true`, runs in the background and wakes Claude on exit code 2.
|
|
412
|
+
| `asyncRewake` | no | If `true`, runs in the background and wakes Claude on exit code 2. The hook’s stderr, or stdout if stderr is empty, is shown to Claude as a [system reminder](https://code.claude.com/docs/en/glossary#system-reminder) so it can react to a long-running background failure |
|
|
334
413
|
| `shell` | no | Shell to use for this hook. Accepts `"bash"` or `"powershell"`. Defaults to `"bash"`, or to `"powershell"` on Windows when Git Bash isn’t installed. Setting `"powershell"` runs the command via PowerShell on Windows. Does not require `CLAUDE_CODE_USE_POWERSHELL_TOOL` since hooks spawn PowerShell directly. Ignored when `args` is set |
|
|
414
|
+
| `onFailure` | no | What happens to the action when the hook fails: `"continue"`, the default, or `"block"`. See [Block the action when a hook fails](https://code.claude.com/docs/en/hooks#block-the-action-when-a-hook-fails). Requires Claude Code v2.1.295 or later |
|
|
335
415
|
|
|
336
416
|
##### Exec form and shell form
|
|
337
417
|
|
|
@@ -358,7 +438,7 @@ The equivalent shell form needs quoting to handle paths with spaces or special c
|
|
|
358
438
|
}
|
|
359
439
|
```
|
|
360
440
|
|
|
361
|
-
Both forms support the same [path placeholders](https://code.claude.com/docs/en/hooks#reference-scripts-by-path), and both export them as the environment variables `CLAUDE_PROJECT_DIR`, `CLAUDE_PLUGIN_ROOT`, and `CLAUDE_PLUGIN_DATA` on the spawned process, so a script can read `process.env.CLAUDE_PLUGIN_ROOT` regardless of how it was launched.
|
|
441
|
+
Both forms support the same [path placeholders](https://code.claude.com/docs/en/hooks#reference-scripts-by-path), and both export them as the environment variables `CLAUDE_PROJECT_DIR`, `CLAUDE_PLUGIN_ROOT`, and `CLAUDE_PLUGIN_DATA` on the spawned process, so a script can read `process.env.CLAUDE_PLUGIN_ROOT` regardless of how it was launched.Plugin hooks additionally substitute [`${user_config.*}`](https://code.claude.com/docs/en/plugins/manifest-reference#user-configuration) values, in exec form only: the value is substituted into `command` and into each `args` element as a plain string, so no shell re-parses it.A shell-form plugin hook whose `command` references `${user_config.*}` fails with an [error](https://code.claude.com/docs/en/errors#plugin-command-references-user-config) instead of running. To use an option value from a shell-form hook, read the `$CLAUDE_PLUGIN_OPTION_<KEY>` environment variable, such as `$CLAUDE_PLUGIN_OPTION_WEBHOOK_URL` for a `webhook_url` option, or set `args` to switch the hook to exec form. Before v2.1.207, shell-form plugin hook commands also substituted `${user_config.*}`.
|
|
362
442
|
|
|
363
443
|
In exec form, `command` is the executable name or path only. If `command` is a bare name with no path separator and contains whitespace alongside `args`, Claude Code logs a warning because the spawn will fail: there is no executable named `node script.js`. Move the extra tokens into `args`. Absolute paths with spaces, such as `C:\Program Files\nodejs\node.exe`, are a single valid executable and don’t trigger the warning.
|
|
364
444
|
|
|
@@ -371,8 +451,9 @@ In addition to the [common fields](https://code.claude.com/docs/en/hooks#common-
|
|
|
371
451
|
| `url` | yes | URL to send the POST request to |
|
|
372
452
|
| `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 |
|
|
373
453
|
| `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 |
|
|
454
|
+
| `onFailure` | no | What happens to the action when the hook fails: `"continue"`, the default, or `"block"`. See [Block the action when a hook fails](https://code.claude.com/docs/en/hooks#block-the-action-when-a-hook-fails). Requires Claude Code v2.1.295 or later |
|
|
374
455
|
|
|
375
|
-
Claude Code sends the hook’s [JSON input](https://code.claude.com/docs/en/hooks#hook-input-and-output) as the POST request body with `Content-Type: application/json`. The response body uses the same [JSON output format](https://code.claude.com/docs/en/hooks#json-output) as command hooks.Error handling differs from command hooks
|
|
456
|
+
Claude Code sends the hook’s [JSON input](https://code.claude.com/docs/en/hooks#hook-input-and-output) as the POST request body with `Content-Type: application/json`. The response body uses the same [JSON output format](https://code.claude.com/docs/en/hooks#json-output) as command hooks.Error handling differs from command hooks; see [HTTP response handling](https://code.claude.com/docs/en/hooks#http-response-handling).This example sends `PreToolUse` events to a local validation service, authenticating with a token from the `MY_TOKEN` environment variable:
|
|
376
457
|
|
|
377
458
|
```
|
|
378
459
|
{
|
|
@@ -403,11 +484,11 @@ In addition to the [common fields](https://code.claude.com/docs/en/hooks#common-
|
|
|
403
484
|
|
|
404
485
|
| Field | Required | Description |
|
|
405
486
|
| --- | --- | --- |
|
|
406
|
-
| `server` | yes | Name of a configured MCP server. For a [plugin-bundled server](https://code.claude.com/docs/en/mcp#plugin-provided-mcp-servers), this is the scoped name `plugin:<plugin-name>:<server-name>`, such as `plugin:my-plugin:db`, not the bare server key
|
|
487
|
+
| `server` | yes | Name of a configured MCP server. For a [plugin-bundled server](https://code.claude.com/docs/en/mcp#plugin-provided-mcp-servers), this is the scoped name `plugin:<plugin-name>:<server-name>`, such as `plugin:my-plugin:db`, not the bare server key |
|
|
407
488
|
| `tool` | yes | Name of the tool to call on that server |
|
|
408
489
|
| `input` | no | Arguments passed to the tool. String values support `${path}` substitution from the hook’s [JSON input](https://code.claude.com/docs/en/hooks#hook-input-and-output), such as `"${tool_input.file_path}"` |
|
|
409
490
|
|
|
410
|
-
|
|
491
|
+
This example calls the `security_scan` tool on the `my_server` MCP server after each `Write` or `Edit`, passing the edited file’s path:
|
|
411
492
|
|
|
412
493
|
```
|
|
413
494
|
{
|
|
@@ -429,6 +510,18 @@ The tool’s text content is treated like command-hook stdout: if it parses as v
|
|
|
429
510
|
}
|
|
430
511
|
```
|
|
431
512
|
|
|
513
|
+
##### How the tool’s result is read
|
|
514
|
+
|
|
515
|
+
Claude Code reads the tool’s text content the same way it reads command-hook stdout, following the [parsing rule under exit code 0](https://code.claude.com/docs/en/hooks#exit-code-0). If the tool returns `isError: true`, the hook produces a non-blocking error and execution continues.
|
|
516
|
+
|
|
517
|
+
##### When the server is still connecting
|
|
518
|
+
|
|
519
|
+
On events where a hook can block or change the result, such as `PreToolUse` or `Stop`, Claude Code waits for a connecting server before it calls the tool, for at most [`MCP_TIMEOUT`](https://code.claude.com/docs/en/env-vars) and within the hook’s own [`timeout`](https://code.claude.com/docs/en/hooks#common-fields). On observational events, such as `Notification` or `SessionEnd`, it doesn’t wait.A server showing the [`cached` status](https://code.claude.com/docs/en/mcp#server-status-detail) connects when the hook calls its tool. If the server isn’t connected at that point, the hook produces a non-blocking error and execution continues. The hook never starts an OAuth flow, so [authenticate the server from `/mcp`](https://code.claude.com/docs/en/mcp#authenticate-with-remote-mcp-servers) first.
|
|
520
|
+
|
|
521
|
+
##### Events that fire before MCP servers are available
|
|
522
|
+
|
|
523
|
+
`SessionStart` at launch, including with `--continue` or `--resume`, and every `Setup` event fire before the session’s MCP servers are available to hooks. Claude Code skips their `mcp_tool` hooks without calling the tool, and the [debug log](https://code.claude.com/docs/en/hooks#debug-hooks) records `mcp_tool hooks are not available for the 'SessionStart' hook event (no MCP client context)`, or the same message naming `Setup`. When `SessionStart` fires again later in the session, after `/clear` or a compaction, its `mcp_tool` hooks run. For anything the session needs at launch, use a `type: "command"` hook on `SessionStart` instead.
|
|
524
|
+
|
|
432
525
|
#### [](https://code.claude.com/docs/en/hooks\#prompt-and-agent-hook-fields) Prompt and agent hook fields
|
|
433
526
|
|
|
434
527
|
In addition to the [common fields](https://code.claude.com/docs/en/hooks#common-fields), prompt and agent hooks accept these fields:
|
|
@@ -436,17 +529,24 @@ In addition to the [common fields](https://code.claude.com/docs/en/hooks#common-
|
|
|
436
529
|
| Field | Required | Description |
|
|
437
530
|
| --- | --- | --- |
|
|
438
531
|
| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON. Escape with a backslash to include literal text: `\$1.00` renders as `$1.00` |
|
|
439
|
-
| `model` | no | Model to use for evaluation. Defaults to
|
|
532
|
+
| `model` | no | Model to use for evaluation. Defaults to the model Claude Code uses for [background functionality](https://code.claude.com/docs/en/costs#background-token-usage) |
|
|
440
533
|
|
|
441
534
|
### [](https://code.claude.com/docs/en/hooks\#reference-scripts-by-path) Reference scripts by path
|
|
442
535
|
|
|
443
536
|
Use these placeholders to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:
|
|
444
537
|
|
|
445
|
-
- `${CLAUDE_PROJECT_DIR}`: the project root. Claude Code also sets this variable in the environment of [stdio MCP servers](https://code.claude.com/docs/en/mcp#option-3-add-a-local-stdio-server) and plugin LSP servers.
|
|
446
|
-
- `${CLAUDE_PLUGIN_ROOT}`: the plugin’s installation directory, for scripts bundled with a [plugin](https://code.claude.com/docs/en/plugins).
|
|
447
|
-
- `${CLAUDE_PLUGIN_DATA}`: the plugin’s [persistent data directory](https://code.claude.com/docs/en/plugins-
|
|
538
|
+
- `${CLAUDE_PROJECT_DIR}`: the project root where the session started. Claude Code also sets this variable in the environment of [stdio MCP servers](https://code.claude.com/docs/en/mcp#option-3-add-a-local-stdio-server) and plugin LSP servers.
|
|
539
|
+
- `${CLAUDE_PLUGIN_ROOT}`: the plugin’s installation directory, for scripts bundled with a [plugin](https://code.claude.com/docs/en/plugins/overview). See [plugin environment variables](https://code.claude.com/docs/en/plugins/manifest-reference#environment-variables) for how the path behaves across updates.
|
|
540
|
+
- `${CLAUDE_PLUGIN_DATA}`: the plugin’s [persistent data directory](https://code.claude.com/docs/en/plugins/components#path-variables-and-persistent-data), for dependencies and state that should survive plugin updates.
|
|
541
|
+
|
|
542
|
+
**Worktrees are different.** If Claude enters a [worktree](https://code.claude.com/docs/en/worktrees) during the session, Claude Code keeps `${CLAUDE_PROJECT_DIR}` where it was and passes the worktree path to your hooks a different way:
|
|
543
|
+
|
|
544
|
+
- **`${CLAUDE_PROJECT_DIR}` stays put**: it still points at the project root where the session started, so a command such as `${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh` still runs the script in the main checkout.
|
|
545
|
+
- **`cwd` follows Claude**: the `cwd` field in the hook’s [input JSON](https://code.claude.com/docs/en/hooks#common-input-fields) is the worktree root after Claude enters a worktree, and the new directory after Claude runs `cd`. Read it when a hook needs to know which directory Claude is working in.
|
|
546
|
+
|
|
547
|
+
For a worktree session that you start from the desktop app, see [What worktrees share with the main checkout](https://code.claude.com/docs/en/worktrees#what-worktrees-share-with-the-main-checkout) for where `${CLAUDE_PROJECT_DIR}` points.
|
|
448
548
|
|
|
449
|
-
Prefer [exec form](https://code.claude.com/docs/en/hooks#exec-form-and-shell-form) for any hook that references a path placeholder.
|
|
549
|
+
Prefer [exec form](https://code.claude.com/docs/en/hooks#exec-form-and-shell-form) for any hook that references a path placeholder. In shell form, wrap each placeholder in double quotes.
|
|
450
550
|
|
|
451
551
|
- Project scripts
|
|
452
552
|
|
|
@@ -497,11 +597,16 @@ Define plugin hooks in `hooks/hooks.json` with an optional top-level `descriptio
|
|
|
497
597
|
}
|
|
498
598
|
```
|
|
499
599
|
|
|
500
|
-
See the [plugin components reference](https://code.claude.com/docs/en/plugins
|
|
600
|
+
See the [plugin components reference](https://code.claude.com/docs/en/plugins/components#hooks) for details on creating plugin hooks.
|
|
501
601
|
|
|
502
602
|
### [](https://code.claude.com/docs/en/hooks\#hooks-in-skills-and-agents) Hooks in skills and agents
|
|
503
603
|
|
|
504
|
-
In addition to settings files and plugins, hooks can be defined directly in [skills](https://code.claude.com/docs/en/skills) and [subagents](https://code.claude.com/docs/en/sub-agents) using frontmatter
|
|
604
|
+
In addition to settings files and plugins, hooks can be defined directly in [skills](https://code.claude.com/docs/en/skills) and [subagents](https://code.claude.com/docs/en/sub-agents) using frontmatter, in the same configuration format as settings-based hooks. How long Claude Code keeps them registered depends on the component:
|
|
605
|
+
|
|
606
|
+
- **Subagent hooks**: Claude Code runs them only while that subagent is running and removes them when it finishes. Claude Code converts a `Stop` hook here to `SubagentStop`, the event it fires when a subagent completes.
|
|
607
|
+
- **Skill hooks**: Claude Code registers them when you or Claude invoke the skill and keeps running them for the rest of the session, on turns after the skill’s own turn as well. To have Claude Code remove a hook after its first successful run instead, set [`once: true`](https://code.claude.com/docs/en/hooks#common-fields) on it.
|
|
608
|
+
|
|
609
|
+
This skill defines a `PreToolUse` hook that runs a security validation script before each `Bash` command:
|
|
505
610
|
|
|
506
611
|
```
|
|
507
612
|
---
|
|
@@ -516,28 +621,19 @@ hooks:
|
|
|
516
621
|
---
|
|
517
622
|
```
|
|
518
623
|
|
|
519
|
-
|
|
624
|
+
Subagents use the same format in their YAML frontmatter.Frontmatter hooks in a project skill follow the same [workspace trust rule as hooks in settings files](https://code.claude.com/docs/en/hooks#workspace-trust). Claude Code registers them when you or Claude invoke the skill, including in a `-p` run in a folder you haven’t trusted.Frontmatter hooks in a project subagent run only after you accept the [workspace trust dialog](https://code.claude.com/docs/en/permissions#project-allow-rules-and-workspace-trust) for the folder the agent file came from. A `-p` session doesn’t count as accepting it. [What runs before you trust a folder](https://code.claude.com/docs/en/permissions#what-runs-before-you-trust-a-folder) compares this with the settings-file rule, and the subagents page lists [which scopes are exempt](https://code.claude.com/docs/en/sub-agents#hooks-in-subagent-frontmatter). Before v2.1.218, these hooks could run from folders you hadn’t trusted.
|
|
520
625
|
|
|
521
626
|
### [](https://code.claude.com/docs/en/hooks\#the-/hooks-menu) The `/hooks` menu
|
|
522
627
|
|
|
523
|
-
Type `/hooks` in Claude Code to open a read-only browser for your configured hooks. The
|
|
524
|
-
|
|
525
|
-
- `User`: from `~/.claude/settings.json`
|
|
526
|
-
- `Project`: from `.claude/settings.json`
|
|
527
|
-
- `Local`: from `.claude/settings.local.json`
|
|
528
|
-
- `Plugin`: from a plugin’s `hooks/hooks.json`
|
|
529
|
-
- `Session`: registered in memory for the current session
|
|
530
|
-
- `Built-in`: registered internally by Claude Code
|
|
531
|
-
|
|
532
|
-
Selecting 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.
|
|
628
|
+
Type `/hooks` in Claude Code to open a read-only browser for your configured hooks. The list labels each hook with where it comes from, such as user settings, project settings, local settings, a plugin, or the current session.Select a hook to see the full text of what it runs and where it’s defined, such as the path of its settings file or the name of its plugin.To browse all hook events, including ones with no hooks configured, select `All events` at the end of the list.
|
|
533
629
|
|
|
534
630
|
### [](https://code.claude.com/docs/en/hooks\#disable-or-remove-hooks) Disable or remove hooks
|
|
535
631
|
|
|
536
|
-
To remove a hook, delete its entry from
|
|
632
|
+
To remove a hook defined in a settings file, delete its entry from that file.To temporarily disable all hooks without removing them, set `"disableAllHooks": true` in your settings file. Claude Code reads the value left after [settings precedence](https://code.claude.com/docs/en/settings#settings-precedence) applies, so a `"disableAllHooks": false` in a project’s `.claude/settings.json` overrides a `true` in your user settings. To turn hooks off for one run whatever the project’s settings say, pass `--settings '{"disableAllHooks": true}'`, which takes precedence over project and local settings. There is no way to disable an individual hook while keeping it in the configuration.The `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 can’t disable those managed hooks. Only `disableAllHooks` set at the managed settings level can disable managed hooks. For the full reach of each level, see [`disableAllHooks`](https://code.claude.com/docs/en/settings-reference#disableallhooks).Direct edits to hooks in settings files are normally picked up automatically by the file watcher.
|
|
537
633
|
|
|
538
634
|
## [](https://code.claude.com/docs/en/hooks\#hook-input-and-output) Hook input and output
|
|
539
635
|
|
|
540
|
-
Command 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](https://code.claude.com/docs/en/hooks#hook-events) includes its specific input schema and decision control options.On macOS and Linux, command hooks run in their own session without a controlling terminal
|
|
636
|
+
Command 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](https://code.claude.com/docs/en/hooks#hook-events) includes its specific input schema and decision control options.On macOS and Linux, command hooks run in their own session without a controlling terminal. The hook process and any child processes can’t open `/dev/tty` or send escape sequences directly to the Claude Code interface. Windows has no `/dev/tty`.To surface a message to the user on any platform, return [`systemMessage`](https://code.claude.com/docs/en/hooks#json-output) in JSON output. Some events discard it or deliver it elsewhere, and each [event’s section](https://code.claude.com/docs/en/hooks#hook-events) says so. To trigger a desktop notification, set a window title, or ring the bell, return [`terminalSequence`](https://code.claude.com/docs/en/hooks#emit-terminal-notifications) instead.
|
|
541
637
|
|
|
542
638
|
### [](https://code.claude.com/docs/en/hooks\#common-input-fields) Common input fields
|
|
543
639
|
|
|
@@ -546,21 +642,22 @@ Hook events receive these fields as JSON, in addition to event-specific fields d
|
|
|
546
642
|
| Field | Description |
|
|
547
643
|
| --- | --- |
|
|
548
644
|
| `session_id` | Current session identifier |
|
|
549
|
-
| `prompt_id` | UUID identifying the user prompt currently being processed. Matches the [`prompt.id` attribute on OpenTelemetry events](https://code.claude.com/docs/en/monitoring-usage#event-correlation-attributes), so you can correlate hook output with telemetry for a single prompt. Absent until the first user input
|
|
645
|
+
| `prompt_id` | UUID identifying the user prompt currently being processed. Matches the [`prompt.id` attribute on OpenTelemetry events](https://code.claude.com/docs/en/monitoring-usage#event-correlation-attributes), so you can correlate hook output with telemetry for a single prompt. Absent until the first user input |
|
|
550
646
|
| `transcript_path` | Path to conversation JSON. The transcript file is written asynchronously and may lag the in-memory conversation, so it may not yet include the current turn’s most recent messages when a hook fires. Hooks that need the final assistant text of the current turn should use `last_assistant_message` on [Stop](https://code.claude.com/docs/en/hooks#stop) and [SubagentStop](https://code.claude.com/docs/en/hooks#subagentstop) instead of reading the transcript |
|
|
551
647
|
| `cwd` | Current working directory when the hook is invoked |
|
|
648
|
+
| `scratchpad_dir` | Path to the session’s [scratchpad directory](https://code.claude.com/docs/en/claude-directory#session-scratchpad-directory), where Claude keeps temporary working files. Absent when the session has no scratchpad or the temp directory is unavailable. Requires Claude Code v2.1.257 or later |
|
|
552
649
|
| `permission_mode` | Current [permission mode](https://code.claude.com/docs/en/permissions#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"auto"`, `"dontAsk"`, or `"bypassPermissions"`. The mode labeled **Manual** arrives as `"default"`, never as `"manual"`, so scripts that match `"default"` keep working. Not all events receive this field. Check the JSON example in each [hook event](https://code.claude.com/docs/en/hooks#hook-events) section |
|
|
553
|
-
| `effort` | Object with a `level` field holding the
|
|
650
|
+
| `effort` | Object with a `level` field holding the [effort level](https://code.claude.com/docs/en/model-config#adjust-effort-level) in effect when the hook runs: `"low"`, `"medium"`, `"high"`, `"xhigh"`, or `"max"`. If you set a level the active model doesn’t support, `level` reports the level Claude Code ran instead; [Adjust effort level](https://code.claude.com/docs/en/model-config#adjust-effort-level) says how it picks that level. The object matches the [status line](https://code.claude.com/docs/en/statusline#available-data)`effort` field. Present for events that fire within a tool-use context, such as `PreToolUse`, `PostToolUse`, `Stop`, and `SubagentStop`, when the current model supports the effort parameter. The level is also available to hook commands and the Bash tool as the `$CLAUDE_EFFORT` environment variable. |
|
|
554
651
|
| `hook_event_name` | Name of the event that fired |
|
|
555
652
|
|
|
556
|
-
|
|
653
|
+
`agent_id` and `agent_type` tell your script which agent a hook fired in, such as a subagent, an [in-process teammate](https://code.claude.com/docs/en/agent-teams#choose-a-display-mode), or the agent you chose with `--agent`:
|
|
557
654
|
|
|
558
655
|
| Field | Description |
|
|
559
656
|
| --- | --- |
|
|
560
|
-
| `agent_id` | Unique identifier for the subagent
|
|
561
|
-
| `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.
|
|
657
|
+
| `agent_id` | Unique identifier for the subagent or in-process teammate the hook fires in. |
|
|
658
|
+
| `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. See [SubagentStart](https://code.claude.com/docs/en/hooks#subagentstart) for the values custom and plugin subagents report and how to write a matcher against a plugin-scoped name. |
|
|
562
659
|
|
|
563
|
-
Only [`SessionStart`](https://code.claude.com/docs/en/hooks#sessionstart) hooks can receive a `model` field, and it
|
|
660
|
+
Only [`SessionStart`](https://code.claude.com/docs/en/hooks#sessionstart) hooks can receive a `model` field, and Claude Code doesn’t always include it. [`PreModelSwitch`](https://code.claude.com/docs/en/hooks#premodelswitch) and [`PostModelSwitch`](https://code.claude.com/docs/en/hooks#postmodelswitch) hooks receive `from_model` and `to_model` instead, so use a PostModelSwitch hook to follow the model as it changes during a session.There is no `$CLAUDE_MODEL` environment variable. The hook can read `$ANTHROPIC_MODEL` if you set it in your shell, but that value doesn’t change when you switch models with `/model` during a session.A hook process inherits the parent environment, apart from the `OTEL_*` exporter variables that Claude Code [removes from every subprocess it spawns](https://code.claude.com/docs/en/monitoring-usage#administrator-configuration) and, when [`CLAUDE_CODE_SUBPROCESS_ENV_SCRUB`](https://code.claude.com/docs/en/env-vars#variables) is set to `1`, the variables it strips. In a session that [gets the HIPAA configuration](https://code.claude.com/docs/en/hipaa-setup#check-how-developers-sign-in-and-connect), Claude Code also [removes Anthropic credentials](https://code.claude.com/docs/en/hipaa-setup#anthropic-credentials-in-commands-hooks-and-mcp-servers) from the hook’s environment.For example, a `PreToolUse` hook for a Bash command receives this on stdin:
|
|
564
661
|
|
|
565
662
|
```
|
|
566
663
|
{
|
|
@@ -568,25 +665,73 @@ Only [`SessionStart`](https://code.claude.com/docs/en/hooks#sessionstart) hooks
|
|
|
568
665
|
"prompt_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
569
666
|
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
|
|
570
667
|
"cwd": "/home/user/my-project",
|
|
668
|
+
"scratchpad_dir": "/tmp/claude-1000/-home-user-my-project/abc123/scratchpad",
|
|
571
669
|
"permission_mode": "default",
|
|
572
670
|
"hook_event_name": "PreToolUse",
|
|
573
671
|
"tool_name": "Bash",
|
|
574
672
|
"tool_input": {
|
|
575
|
-
"command": "npm test"
|
|
576
|
-
|
|
673
|
+
"command": "npm test",
|
|
674
|
+
"description": "Run test suite",
|
|
675
|
+
"timeout": 120000,
|
|
676
|
+
"run_in_background": false
|
|
677
|
+
},
|
|
678
|
+
"tool_use_id": "toolu_01ABC123..."
|
|
577
679
|
}
|
|
578
680
|
```
|
|
579
681
|
|
|
580
|
-
The `tool_name` and `
|
|
682
|
+
The `tool_name`, `tool_input`, and `tool_use_id` fields are event-specific. Each [hook event](https://code.claude.com/docs/en/hooks#hook-events) section documents the additional fields for that event.
|
|
581
683
|
|
|
582
684
|
### [](https://code.claude.com/docs/en/hooks\#exit-code-output) Exit code output
|
|
583
685
|
|
|
584
|
-
|
|
686
|
+
Your hook’s exit code tells Claude Code whether to continue with the action that triggered the hook, such as a tool call or a prompt. A run that finishes has one of three outcomes:
|
|
687
|
+
|
|
688
|
+
- **Success**: your hook exits 0. Claude Code applies any [JSON output](https://code.claude.com/docs/en/hooks#json-output) fields your hook printed, and the action goes ahead unless those fields block or deny it.
|
|
689
|
+
- **Blocking error**: your hook exits 2. On [events that can block](https://code.claude.com/docs/en/hooks#exit-code-2-behavior-per-event), Claude Code stops the action.
|
|
690
|
+
- **Non-blocking error**: your hook exits with any other code, or fails in some other way, such as not starting or printing invalid JSON. The action goes ahead, and on events such as `PreToolUse` you see a `<hook name> hook error` notice in the transcript. If you want a failed hook to block the action, set [`onFailure: "block"`](https://code.claude.com/docs/en/hooks#block-the-action-when-a-hook-fails).
|
|
691
|
+
|
|
692
|
+
What your hook prints to stdout can change the outcome. For example, if a `PreToolUse` hook exits 1 but prints JSON that passes validation, the run is a success and the JSON fields decide what happens. To find your hook’s outcome on an event such as `PreToolUse`, match what it printed to stdout in the first column with its exit code along the top:
|
|
693
|
+
|
|
694
|
+
| Stdout | Exit 0 | Exit 2 | Any other exit code |
|
|
695
|
+
| --- | --- | --- | --- |
|
|
696
|
+
| JSON object that passes [schema validation](https://code.claude.com/docs/en/hooks#json-output) | Success. The fields apply | Blocking error. Claude Code still reads the fields, but they can’t override the block | Success. Claude Code ignores the exit code, and the fields alone decide. With [`onFailure: "block"`](https://code.claude.com/docs/en/hooks#block-the-action-when-a-hook-fails), this counts as a failure |
|
|
697
|
+
| JSON that [can’t be parsed](https://code.claude.com/docs/en/hooks#exit-code-0) or fails schema validation | Non-blocking error. The notice carries the parse or validation message | Blocking error. Your stderr is the reason | Non-blocking error. The notice carries the parse or validation message |
|
|
698
|
+
| [Plain text](https://code.claude.com/docs/en/hooks#exit-code-0), or nothing | Success | Blocking error. Your stderr is the reason | Non-blocking error. The notice carries the first line of your stderr |
|
|
699
|
+
|
|
700
|
+
Some events have their own rules:
|
|
701
|
+
|
|
702
|
+
- **`WorktreeCreate`**: any non-zero exit code makes worktree creation fail, whatever your JSON says.
|
|
703
|
+
- **`WorktreeRemove`**: any non-zero exit code makes worktree removal fail if the directory still exists afterward.
|
|
704
|
+
- **`Stop`, `SubagentStop`, `TaskCompleted`, and a plugin’s `UserPromptSubmit` hook**: when your hook exits 2 with nothing on stdout and its stderr says a file is missing, such as `No such file or directory`, Claude Code treats the run as a non-blocking error.
|
|
705
|
+
- **`Elicitation` and `ElicitationResult`**: Claude Code applies your `hookSpecificOutput` when your hook exits 0, and ignores it on any other exit code.
|
|
706
|
+
- **Events that discard hook output, such as `StopFailure`**: Claude Code ignores your JSON on any exit code, apart from side-effect fields like `terminalSequence`, which still fire.
|
|
707
|
+
|
|
708
|
+
To check what exit code 2 does on your event, see [Exit code 2 behavior per event](https://code.claude.com/docs/en/hooks#exit-code-2-behavior-per-event). To check which decision fields it honors, see [Decision control](https://code.claude.com/docs/en/hooks#decision-control).
|
|
709
|
+
|
|
710
|
+
#### [](https://code.claude.com/docs/en/hooks\#exit-code-0) Exit code 0
|
|
711
|
+
|
|
712
|
+
Exit 0 means success, and is the intended exit code when you print JSON for structured control.For most events, Claude Code writes stdout to the debug log and doesn’t show it in the transcript. The exceptions are `UserPromptSubmit`, `UserPromptExpansion`, `SessionStart`, and `PostModelSwitch`, where Claude Code adds plain-text stdout as context that Claude can see and act on.For a hook that isn’t [async](https://code.claude.com/docs/en/hooks#how-async-hooks-execute), Claude Code parses your stdout as [JSON output](https://code.claude.com/docs/en/hooks#json-output) when the whole output is one JSON object with nothing around it but whitespace, and otherwise as plain text or a parse failure:
|
|
713
|
+
|
|
714
|
+
- **One JSON object, on one line or several**: parsed as JSON output.
|
|
715
|
+
- **Output that doesn’t start with `{`, or that starts with `{` and doesn’t end with `}`**: plain text. A JSON array and a quoted JSON string are plain text by this rule.
|
|
716
|
+
- **Two or more lines that each parse as JSON on their own, the first starting with `{` and the last ending with `}`**: plain text when no line is a JSON output object that sets a field, and a parse failure when one is.
|
|
717
|
+
- **Anything else that starts with `{` and ends with `}` but isn’t valid JSON**: a parse failure.
|
|
718
|
+
|
|
719
|
+
When Claude Code tries to parse your stdout as JSON and can’t, or the parsed object fails [schema validation](https://code.claude.com/docs/en/hooks#json-output), the run is a [non-blocking error](https://code.claude.com/docs/en/hooks#exit-code-output). The `<hook name> hook error` notice carries the parse or validation message. On the events that add plain-text stdout as context, Claude Code doesn’t add stdout it failed to parse.Claude never sees stderr from a hook that exits 0. To read it yourself on events such as `PreToolUse`, enable [debug logging](https://code.claude.com/docs/en/hooks#debug-hooks). To surface a warning to Claude from a `PostToolUse` or `PostToolUseFailure` hook, exit 2 instead so [Claude sees the stderr](https://code.claude.com/docs/en/hooks#exit-code-2-behavior-per-event) even though the tool already ran.
|
|
720
|
+
|
|
721
|
+
#### [](https://code.claude.com/docs/en/hooks\#exit-code-2) Exit code 2
|
|
722
|
+
|
|
723
|
+
Exit with code 2 to block the action. On [events that can block](https://code.claude.com/docs/en/hooks#exit-code-2-behavior-per-event), Claude Code stops the action: a `PreToolUse` hook blocks the tool call, for example, and a `UserPromptSubmit` hook rejects the prompt.The message that comes with the block is your hook’s stderr. If your hook also printed JSON that makes a blocking decision, Claude Code uses that decision’s reason instead.Exit 2 blocks even when your hook prints JSON:
|
|
724
|
+
|
|
725
|
+
- **JSON that passes schema validation**: Claude Code still reads the [JSON output](https://code.claude.com/docs/en/hooks#json-output) fields, but they can’t override the block. Even a `permissionDecision` of `"allow"` doesn’t let the action through. On `Elicitation` and `ElicitationResult`, an exit-2 hook’s `hookSpecificOutput` is ignored.
|
|
726
|
+
- **JSON that fails schema validation**: the hook still blocks. Claude Code uses your stderr as the blocking reason and records the validation failure in the debug log.
|
|
727
|
+
|
|
728
|
+
This script blocks `rm` commands by exiting 2 and leaves every other command to the normal permission flow:
|
|
585
729
|
|
|
586
730
|
```
|
|
587
731
|
#!/bin/bash
|
|
588
732
|
# Reads JSON input from stdin, checks the command
|
|
589
|
-
|
|
733
|
+
input=$(cat)
|
|
734
|
+
command=$(jq -r '.tool_input.command' <<<"$input")
|
|
590
735
|
|
|
591
736
|
if [[ "$command" == rm* ]]; then
|
|
592
737
|
echo "Blocked: rm commands are not allowed" >&2
|
|
@@ -596,7 +741,74 @@ fi
|
|
|
596
741
|
exit 0 # No decision: the normal permission flow applies
|
|
597
742
|
```
|
|
598
743
|
|
|
599
|
-
|
|
744
|
+
With this script registered as a `PreToolUse` hook on `Bash`, a command that starts with `rm` is blocked, and Claude receives the hook’s stderr as the tool’s error, prefixed with the event name, the tool name, and the hook’s command:
|
|
745
|
+
|
|
746
|
+
```
|
|
747
|
+
PreToolUse:Bash hook error: [${CLAUDE_PROJECT_DIR}/.claude/hooks/no-rm.sh]: Blocked: rm commands are not allowed
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
#### [](https://code.claude.com/docs/en/hooks\#other-exit-codes) Other exit codes
|
|
751
|
+
|
|
752
|
+
When your hook exits with a code other than 0 or 2 and prints plain text or nothing to stdout, the run is a [non-blocking error](https://code.claude.com/docs/en/hooks#exit-code-output). You see a `<hook name> hook error` notice in the transcript with `Failed with non-blocking status code:` and the first line of your hook’s stderr. For example, when a `PreToolUse` hook on `Bash` prints `something broke` to stderr and exits 1, the `PreToolUse:Bash hook error` notice carries this line:
|
|
753
|
+
|
|
754
|
+
```
|
|
755
|
+
Failed with non-blocking status code: something broke
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
To capture the full stderr rather than its first line, enable [debug logging](https://code.claude.com/docs/en/hooks#debug-hooks).A hook that can’t start is a non-blocking error too. In shell form, when the script path doesn’t exist or isn’t executable, the shell exits with a code like 127 and the notice carries the interpreter’s message, for example `Failed with non-blocking status code: /bin/sh: /path/to/hook.sh: No such file or directory`. When you set up a policy hook, watch for this notice on its first run, because a mistyped path in `settings.json` means the hook never runs. To block the action instead, set [`onFailure: "block"`](https://code.claude.com/docs/en/hooks#block-the-action-when-a-hook-fails).
|
|
759
|
+
|
|
760
|
+
Without valid JSON on stdout, Claude Code treats exit code 1 as a non-blocking error, even though 1 is the conventional Unix failure code. If your hook is meant to enforce a policy, use `exit 2`.
|
|
761
|
+
|
|
762
|
+
#### [](https://code.claude.com/docs/en/hooks\#timeouts) Timeouts
|
|
763
|
+
|
|
764
|
+
Apart from a command hook you run with [`async: true`](https://code.claude.com/docs/en/hooks#run-hooks-in-the-background), Claude Code cancels a `command`, `http`, or `mcp_tool` hook that reaches its [`timeout`](https://code.claude.com/docs/en/hooks#common-fields), discarding the hook’s output, so on most events a timed-out hook renders no decision.On [`PreModelSwitch`](https://code.claude.com/docs/en/hooks#premodelswitch), a hook canceled at its timeout blocks the model switch. On `PreToolUse`, the two hook families differ:
|
|
765
|
+
|
|
766
|
+
- A timed-out `command`, `http`, or `mcp_tool` hook doesn’t block the tool call. The call continues through the normal [permission flow](https://code.claude.com/docs/en/permissions), so don’t count on a stalled hook to act as a gate. To block the call when a `command` or `http` hook times out, set [`onFailure: "block"`](https://code.claude.com/docs/en/hooks#block-the-action-when-a-hook-fails).
|
|
767
|
+
- An [Agent SDK callback hook](https://code.claude.com/docs/en/agent-sdk/hooks) that exceeds its timeout [blocks the tool call](https://code.claude.com/docs/en/hooks#pretooluse).
|
|
768
|
+
|
|
769
|
+
#### [](https://code.claude.com/docs/en/hooks\#block-the-action-when-a-hook-fails) Block the action when a hook fails
|
|
770
|
+
|
|
771
|
+
On most events, when a hook fails or times out, Claude Code still carries out the action, so a policy hook with a wrong path or a crashing script lets everything through. To block the action instead, set `"onFailure": "block"` on a `command` or `http` hook. The default value is `"continue"`. Requires Claude Code v2.1.295 or later.This `PreToolUse` hook in `.claude/settings.json` runs a project script before each Bash command, and blocks the command if the script fails:
|
|
772
|
+
|
|
773
|
+
```
|
|
774
|
+
{
|
|
775
|
+
"hooks": {
|
|
776
|
+
"PreToolUse": [\
|
|
777
|
+
{\
|
|
778
|
+
"matcher": "Bash",\
|
|
779
|
+
"hooks": [\
|
|
780
|
+
{\
|
|
781
|
+
"type": "command",\
|
|
782
|
+
"command": "node",\
|
|
783
|
+
"args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.js"],\
|
|
784
|
+
"onFailure": "block"\
|
|
785
|
+
}\
|
|
786
|
+
]\
|
|
787
|
+
}\
|
|
788
|
+
]
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
To test it, leave `check-command.js` missing and ask Claude to run a Bash command such as `ls`. Claude Code blocks the call, and the error includes `failed; blocking because onFailure is "block"` followed by node’s own error output, trimmed here to one line:
|
|
794
|
+
|
|
795
|
+
```
|
|
796
|
+
PreToolUse:Bash hook error: [node ${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.js]: failed; blocking because onFailure is "block"
|
|
797
|
+
Error: Cannot find module '/path/to/project/.claude/hooks/check-command.js'
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
After a timeout, the message says `timed out` instead of `failed`. Without `onFailure` set, the same missing script is a non-blocking error and `ls` runs.Each of these counts as a failure:
|
|
801
|
+
|
|
802
|
+
- **Can’t start**: a command hook fails to start, for example because the script or executable doesn’t exist
|
|
803
|
+
- **Exit code other than 0 or 2**: counts for a command hook even if it printed JSON that allows the action, such as `permissionDecision: "allow"`. To return a JSON decision, exit 0
|
|
804
|
+
- **HTTP error**: an HTTP hook’s connection fails, or the response status isn’t 2xx
|
|
805
|
+
- **Timeout**: the hook reaches its [`timeout`](https://code.claude.com/docs/en/hooks#common-fields)
|
|
806
|
+
- **Invalid output**: the JSON output [can’t be parsed](https://code.claude.com/docs/en/hooks#exit-code-0) or fails [schema validation](https://code.claude.com/docs/en/hooks#json-output). For an HTTP hook, a 2xx body that is neither empty nor a JSON object also counts. Plain-text stdout from a command hook isn’t a failure
|
|
807
|
+
|
|
808
|
+
With `"block"` set, a failure does what [exit code 2 does on that event](https://code.claude.com/docs/en/hooks#exit-code-2-behavior-per-event), except on `PermissionRequest`, where it denies the request. For example, a `PreToolUse` failure blocks the tool call and a `UserPromptSubmit` failure blocks the prompt.The field has no effect on these hooks:
|
|
809
|
+
|
|
810
|
+
- **`Stop`, `SubagentStop`, `TaskCompleted`, and `TeammateIdle` hooks**: exit code 2 on these events sends Claude back to keep working, and Claude can’t repair a hook that won’t run
|
|
811
|
+
- **Background command hooks**: command hooks that set [`async` or `asyncRewake`](https://code.claude.com/docs/en/hooks#run-hooks-in-the-background)
|
|
600
812
|
|
|
601
813
|
#### [](https://code.claude.com/docs/en/hooks\#exit-code-2-behavior-per-event) Exit code 2 behavior per event
|
|
602
814
|
|
|
@@ -605,8 +817,8 @@ Exit code 2 is the way a hook signals “stop, don’t do this.” The effect de
|
|
|
605
817
|
| Hook event | Can block? | What happens on exit 2 |
|
|
606
818
|
| --- | --- | --- |
|
|
607
819
|
| `PreToolUse` | Yes | Blocks the tool call |
|
|
608
|
-
| `PermissionRequest` |
|
|
609
|
-
| `UserPromptSubmit` | Yes | Blocks prompt
|
|
820
|
+
| `PermissionRequest` | No | Exit code 2 isn’t honored for this event and the permission flow proceeds unchanged. Deny through the [`decision` object](https://code.claude.com/docs/en/hooks#permissionrequest-decision-control) instead |
|
|
821
|
+
| `UserPromptSubmit` | Yes | Blocks the prompt, so it never reaches Claude. See [What a blocked prompt leaves behind](https://code.claude.com/docs/en/hooks#what-a-blocked-prompt-leaves-behind) |
|
|
610
822
|
| `UserPromptExpansion` | Yes | Blocks the expansion |
|
|
611
823
|
| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |
|
|
612
824
|
| `SubagentStop` | Yes | Prevents the subagent from stopping |
|
|
@@ -614,70 +826,82 @@ Exit code 2 is the way a hook signals “stop, don’t do this.” The effect de
|
|
|
614
826
|
| `TaskCreated` | Yes | Rolls back the task creation |
|
|
615
827
|
| `TaskCompleted` | Yes | Prevents the task from being marked as completed |
|
|
616
828
|
| `ConfigChange` | Yes | Blocks the configuration change from taking effect (except `policy_settings`) |
|
|
617
|
-
| `StopFailure` | No | Output and exit code are ignored |
|
|
829
|
+
| `StopFailure` | No | Output and exit code are ignored, except `terminalSequence` |
|
|
618
830
|
| `PostToolUse` | No | Shows stderr to Claude; the tool already ran |
|
|
619
831
|
| `PostToolUseFailure` | No | Shows stderr to Claude; the tool already failed |
|
|
620
832
|
| `PostToolBatch` | Yes | Stops the agentic loop before the next model call |
|
|
621
|
-
| `PermissionDenied` | No | Exit code and stderr are ignored because the denial already occurred. Use JSON `hookSpecificOutput.retry: true` to tell the model it may retry |
|
|
622
|
-
| `Notification` | No |
|
|
833
|
+
| `PermissionDenied` | No | Exit code and stderr are ignored because the denial already occurred. Use JSON `hookSpecificOutput.retry: true` to tell the model it may retry; Claude Code ignores `retry: true` for [no-verdict denials](https://code.claude.com/docs/en/hooks#permissiondenied-decision-control) |
|
|
834
|
+
| `Notification` | No | Exit code and stderr are ignored |
|
|
623
835
|
| `SubagentStart` | No | Shows stderr to user only |
|
|
624
836
|
| `SessionStart` | No | Shows stderr to user only |
|
|
625
|
-
| `Setup` | No |
|
|
837
|
+
| `Setup` | No | Exit code and stderr are ignored |
|
|
626
838
|
| `SessionEnd` | No | Shows stderr to user only |
|
|
627
839
|
| `CwdChanged` | No | Shows stderr to user only |
|
|
840
|
+
| `DirectoryAdded` | No | Stderr goes to the debug log; the directory is already added |
|
|
628
841
|
| `FileChanged` | No | Shows stderr to user only |
|
|
629
842
|
| `PreCompact` | Yes | Blocks compaction |
|
|
630
843
|
| `PostCompact` | No | Shows stderr to user only |
|
|
631
|
-
| `
|
|
844
|
+
| `PreModelSwitch` | Yes | Blocks the model switch and shows stderr to the user |
|
|
845
|
+
| `PostModelSwitch` | No | Shows stderr to user only; the model already switched |
|
|
846
|
+
| `Elicitation` | Yes | Declines the request, and no dialog appears |
|
|
632
847
|
| `ElicitationResult` | Yes | Blocks the response (action becomes decline) |
|
|
633
848
|
| `WorktreeCreate` | Yes | Any non-zero exit code causes worktree creation to fail |
|
|
634
|
-
| `WorktreeRemove` |
|
|
849
|
+
| `WorktreeRemove` | Yes | Any non-zero exit code causes worktree removal to fail if the directory still exists afterward. See [WorktreeRemove](https://code.claude.com/docs/en/hooks#worktreeremove) for what happens to the directory |
|
|
635
850
|
| `InstructionsLoaded` | No | Exit code is ignored |
|
|
636
851
|
| `MessageDisplay` | No | The original text is displayed |
|
|
637
852
|
|
|
638
|
-
For `SessionStart`, `
|
|
853
|
+
For `SessionStart`, `SubagentStart`, and `PostModelSwitch`, Claude Code renders the exit code 2 stderr in the transcript as a `<hook name> hook error` notice, the same way it renders a [non-blocking error](https://code.claude.com/docs/en/hooks#exit-code-output). Claude doesn’t see it, and the session or subagent proceeds. For `SubagentStart`, the notice appears in the subagent’s own transcript, not in the parent conversation.
|
|
639
854
|
|
|
640
855
|
### [](https://code.claude.com/docs/en/hooks\#http-response-handling) HTTP response handling
|
|
641
856
|
|
|
642
|
-
HTTP hooks use HTTP status codes and response bodies instead of exit codes and stdout:
|
|
857
|
+
HTTP hooks use HTTP status codes and response bodies instead of exit codes and stdout. The outcomes below apply to most events; an event with its own failure contract in the [per-event table](https://code.claude.com/docs/en/hooks#exit-code-2-behavior-per-event), such as `WorktreeCreate`, applies that contract to a failed HTTP hook too:
|
|
643
858
|
|
|
644
859
|
- **2xx with an empty body**: success, equivalent to exit code 0 with no output
|
|
645
|
-
- **2xx with a
|
|
646
|
-
- **2xx with
|
|
860
|
+
- **2xx with a JSON object body**: parsed using the same [JSON output](https://code.claude.com/docs/en/hooks#json-output) schema as command hooks. A body that fails schema validation is a non-blocking error
|
|
861
|
+
- **2xx with any other body, such as plain text**: non-blocking error, handled the same as a non-2xx status. Claude Code doesn’t add the text to Claude’s context
|
|
647
862
|
- **Non-2xx status**: non-blocking error, execution continues
|
|
648
|
-
- **Connection failure
|
|
863
|
+
- **Connection failure**: non-blocking error, execution continues
|
|
864
|
+
- **Timeout**: the hook is canceled, as described under [Timeouts](https://code.claude.com/docs/en/hooks#timeouts)
|
|
649
865
|
|
|
650
|
-
|
|
866
|
+
HTTP hooks can’t signal a blocking error through the status code alone: a non-2xx status or a failed connection is a [non-blocking error](https://code.claude.com/docs/en/hooks#exit-code-output). To block a tool call or deny a permission, return a 2xx response with a JSON body containing the appropriate decision fields. To block the action when the request fails or returns a non-2xx status, set [`onFailure: "block"`](https://code.claude.com/docs/en/hooks#block-the-action-when-a-hook-fails).
|
|
651
867
|
|
|
652
868
|
### [](https://code.claude.com/docs/en/hooks\#json-output) JSON output
|
|
653
869
|
|
|
654
870
|
Exit codes only let you block or stay silent, but JSON output gives you finer-grained control. Instead of exiting with code 2 to block, exit 0 and print a JSON object to stdout. Claude Code reads specific fields from that JSON to control behavior, including [decision control](https://code.claude.com/docs/en/hooks#decision-control) for blocking, allowing, or escalating to the user.
|
|
655
871
|
|
|
656
|
-
|
|
872
|
+
Choose one approach per hook: either use exit codes alone for signaling, or exit 0 and print JSON for structured control. If you mix them, exit 2 keeps its [blocking effect](https://code.claude.com/docs/en/hooks#exit-code-2-behavior-per-event), and Claude Code still reads the JSON fields, with the one elicitation exception noted under [Exit code 2](https://code.claude.com/docs/en/hooks#exit-code-2).
|
|
873
|
+
|
|
874
|
+
Print nothing but the JSON object to stdout. For a hook that isn’t [async](https://code.claude.com/docs/en/hooks#how-async-hooks-execute), other text there, such as a line your shell profile echoes at startup, keeps Claude Code from reading the object as JSON, and [Hook JSON has no effect](https://code.claude.com/docs/en/hooks-guide#hook-json-has-no-effect) shows how to find and silence that text.A hook’s `additionalContext`, `systemMessage`, and `initialUserMessage` strings, and its plain stdout, are capped at 10,000 characters:
|
|
657
875
|
|
|
658
|
-
|
|
876
|
+
- **Scope**: Claude Code measures each string on its own, even when several hooks run for the same event. For JSON output, each field is measured separately; plain stdout is measured whole.
|
|
877
|
+
- **Over the limit**: Claude Code saves the output to a file in the session directory and replaces it with the file path and a preview of up to the first 2,000 characters. A large valid Bash result is handled the same way, described under [Output limits](https://code.claude.com/docs/en/tools-reference#output-limits). Unlike that Bash ceiling, this cap has no setting or environment variable to raise it.
|
|
878
|
+
- **Reading the file**: Claude Code doesn’t ask Claude to read the file, so keep anything Claude must always see within the cap.
|
|
659
879
|
|
|
660
|
-
|
|
880
|
+
The JSON object supports three kinds of fields:
|
|
881
|
+
|
|
882
|
+
- **Universal fields** like `continue` are listed in the table below. Every event accepts them, but some events discard them or deliver `systemMessage` somewhere other than the transcript. Each event’s section says so. `terminalSequence` works on those events too, with the exceptions listed under [Emit terminal notifications](https://code.claude.com/docs/en/hooks#emit-terminal-notifications).
|
|
661
883
|
- **Top-level `decision` and `reason`** are used by some events to block or provide feedback.
|
|
662
884
|
- **`hookSpecificOutput`** is a nested object for events that need richer control. It requires a `hookEventName` field set to the event name.
|
|
663
885
|
|
|
664
886
|
| Field | Default | Description |
|
|
665
887
|
| --- | --- | --- |
|
|
666
888
|
| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over any event-specific decision fields |
|
|
667
|
-
| `stopReason` | none | Message shown to the user when `continue` is `false`.
|
|
668
|
-
| `suppressOutput` | `false` |
|
|
669
|
-
| `systemMessage` | none | Warning message shown to the user |
|
|
889
|
+
| `stopReason` | none | Message shown to the user when `continue` is `false`. It stays in the conversation, so Claude sees it if the conversation continues |
|
|
890
|
+
| `suppressOutput` | `false` | Has no effect: Claude Code accepts the field but doesn’t act on it. A successful hook’s stdout is never shown in the transcript and is recorded in the debug log |
|
|
891
|
+
| `systemMessage` | none | Warning message shown to the user. In [Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview) and [`--output-format stream-json`](https://code.claude.com/docs/en/headless) output, it can arrive as an [`SDKInformationalMessage`](https://code.claude.com/docs/en/agent-sdk/typescript#sdkinformationalmessage) |
|
|
670
892
|
| `terminalSequence` | none | A terminal escape sequence for Claude Code to emit on your behalf, such as a desktop notification, window title, or bell. Restricted to OSC `0`/`1`/`2`/`9`/`99`/`777` and BEL. If the value contains anything outside the allowlist, the field is ignored. Use this instead of writing to `/dev/tty`, which is unavailable to hooks |
|
|
671
893
|
|
|
672
|
-
To stop Claude entirely
|
|
894
|
+
To stop Claude entirely:
|
|
673
895
|
|
|
674
896
|
```
|
|
675
897
|
{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
|
|
676
898
|
```
|
|
677
899
|
|
|
900
|
+
For `PreToolUse` and `PostToolUse` hooks, the stop applies even when the tool call fails or completes while Claude is still streaming a response.
|
|
901
|
+
|
|
678
902
|
#### [](https://code.claude.com/docs/en/hooks\#emit-terminal-notifications) Emit terminal notifications
|
|
679
903
|
|
|
680
|
-
|
|
904
|
+
Hooks run without a controlling terminal, so writing escape sequences directly to `/dev/tty` fails. Instead, return the escape sequence in the `terminalSequence` field and Claude Code emits it for you through its own terminal write path. This is race-free, works inside tmux and GNU screen, and works on Windows where there is no `/dev/tty`.The field accepts a string of one or more allowlisted escape sequences:
|
|
681
905
|
|
|
682
906
|
- OSC `0`, `1`, `2`: window and icon titles
|
|
683
907
|
- OSC `9`: iTerm2, ConEmu, Windows Terminal, and WezTerm notifications, including `9;4` taskbar progress
|
|
@@ -685,7 +909,12 @@ The `terminalSequence` field requires Claude Code v2.1.141 or later.Hooks run wi
|
|
|
685
909
|
- OSC `777`: urxvt, Ghostty, and Warp notifications
|
|
686
910
|
- Bare BEL
|
|
687
911
|
|
|
688
|
-
Sequences may be terminated with BEL or with ST. Anything outside the allowlist, including CSI cursor and color sequences, OSC palette sequences, OSC 8 hyperlinks, OSC 52 clipboard writes, and OSC 1337, is rejected and the field is ignored.
|
|
912
|
+
Sequences may be terminated with BEL or with ST. Anything outside the allowlist, including CSI cursor and color sequences, OSC palette sequences, OSC 8 hyperlinks, OSC 52 clipboard writes, and OSC 1337, is rejected and the field is ignored.Claude Code writes the sequence itself when it processes your hook’s output, so the field works on events that discard `systemMessage` and `continue`, such as `Notification` and `StopFailure`. It has two limits:
|
|
913
|
+
|
|
914
|
+
- Claude Code writes the sequence only in an interactive session, and only while its interface is on screen. In non-interactive mode with the `-p` flag and in the Agent SDK, it ignores the field.
|
|
915
|
+
- A `WorktreeCreate` command hook can’t return JSON, because Claude Code reads its stdout as the worktree path. An HTTP `WorktreeCreate` hook returns JSON and can include the field.
|
|
916
|
+
|
|
917
|
+
The example below fires a desktop notification from a `Notification` hook. The escape sequence is built with `printf` octal escapes so the control bytes never appear on the shell command line, and `jq -n --arg` builds the JSON output so quotes, backslashes, and newlines in the notification message are escaped correctly:
|
|
689
918
|
|
|
690
919
|
```
|
|
691
920
|
#!/bin/bash
|
|
@@ -697,13 +926,11 @@ seq=$(printf '\033]777;notify;%s;%s\007' "$title" "$body")
|
|
|
697
926
|
jq -nc --arg seq "$seq" '{terminalSequence: $seq}'
|
|
698
927
|
```
|
|
699
928
|
|
|
700
|
-
The `{ "terminalSequence": "..." }` shape is the same from any shell or language.
|
|
701
|
-
|
|
702
|
-
`terminalSequence` is the supported replacement for hooks that previously wrote escape sequences directly to `/dev/tty`. The allowlist is restricted to sequences that can’t move the cursor or alter colors, so a hook can never corrupt an on-screen prompt.
|
|
929
|
+
The `{ "terminalSequence": "..." }` shape is the same from any shell or language.
|
|
703
930
|
|
|
704
931
|
#### [](https://code.claude.com/docs/en/hooks\#add-context-for-claude) Add context for Claude
|
|
705
932
|
|
|
706
|
-
The `additionalContext` field passes a string from your hook into Claude’s context window. Claude Code wraps the string in a system reminder and inserts it into the conversation at the point where the hook fired. Claude reads the reminder on the next model request, but it doesn’t appear as a chat message in the interface.Return `additionalContext` inside `hookSpecificOutput` alongside the event name:
|
|
933
|
+
The `additionalContext` field passes a string from your hook into Claude’s context window. Claude Code wraps the string in a [system reminder](https://code.claude.com/docs/en/glossary#system-reminder) and inserts it into the conversation at the point where the hook fired. Claude reads the reminder on the next model request, but it doesn’t appear as a chat message in the interface.Return `additionalContext` inside `hookSpecificOutput` alongside the event name:
|
|
707
934
|
|
|
708
935
|
```
|
|
709
936
|
{
|
|
@@ -716,18 +943,19 @@ The `additionalContext` field passes a string from your hook into Claude’s con
|
|
|
716
943
|
|
|
717
944
|
Where the reminder appears depends on the event:
|
|
718
945
|
|
|
719
|
-
- [SessionStart](https://code.claude.com/docs/en/hooks#sessionstart)
|
|
946
|
+
- [SessionStart](https://code.claude.com/docs/en/hooks#sessionstart) and [SubagentStart](https://code.claude.com/docs/en/hooks#subagentstart): at the start of the conversation, before the first prompt
|
|
720
947
|
- [UserPromptSubmit](https://code.claude.com/docs/en/hooks#userpromptsubmit) and [UserPromptExpansion](https://code.claude.com/docs/en/hooks#userpromptexpansion): alongside the submitted prompt
|
|
721
948
|
- [PreToolUse](https://code.claude.com/docs/en/hooks#pretooluse), [PostToolUse](https://code.claude.com/docs/en/hooks#posttooluse), [PostToolUseFailure](https://code.claude.com/docs/en/hooks#posttoolusefailure), and [PostToolBatch](https://code.claude.com/docs/en/hooks#posttoolbatch): next to the tool result
|
|
722
949
|
- [Stop](https://code.claude.com/docs/en/hooks#stop) and [SubagentStop](https://code.claude.com/docs/en/hooks#subagentstop): at the end of the turn. The conversation continues so Claude can act on the feedback. See [Stop decision control](https://code.claude.com/docs/en/hooks#stop-decision-control)
|
|
950
|
+
- [PostModelSwitch](https://code.claude.com/docs/en/hooks#postmodelswitch): with the next request after the switch. See [PostModelSwitch decision control](https://code.claude.com/docs/en/hooks#postmodelswitch-decision-control) for timing
|
|
723
951
|
|
|
724
|
-
When several hooks return `additionalContext` for the same event, Claude receives all of the values. If a value exceeds 10,000 characters, Claude Code writes the
|
|
952
|
+
When several hooks return `additionalContext` for the same event, Claude receives all of the values.If your string contains a `<system-reminder>` or `</system-reminder>` tag, Claude receives the string with that tag’s `<` replaced by `<`.If a value exceeds 10,000 characters, Claude Code writes the text to a file in the session directory and passes Claude the file path with a preview of up to the first 2,000 characters instead. Claude can read the file, but Claude Code doesn’t ask it to.Use `additionalContext` for information Claude should know about the current state of your environment or the operation that just ran:
|
|
725
953
|
|
|
726
954
|
- **Environment state**: the current branch, deployment target, or active feature flags
|
|
727
955
|
- **Conditional project rules**: which test command applies to the file just edited, which directories are read-only in this worktree
|
|
728
956
|
- **External data**: open issues assigned to you, recent CI results, content fetched from an internal service
|
|
729
957
|
|
|
730
|
-
For instructions that never change, prefer [CLAUDE.md](https://code.claude.com/docs/en/memory). It loads without running a script and is the standard place for static project conventions.Write the text as factual statements rather than imperative system instructions. Phrasing such as “The deployment target is production” or “This repo uses `bun test`” reads as project information. Text framed as out-of-band system commands can trigger Claude’s prompt-injection defenses, which causes Claude to surface the text to you instead of treating it as context.
|
|
958
|
+
For instructions that never change, prefer [CLAUDE.md](https://code.claude.com/docs/en/memory). It loads without running a script and is the standard place for static project conventions.Write the text as factual statements rather than imperative system instructions. Phrasing such as “The deployment target is production” or “This repo uses `bun test`” reads as project information. Text framed as out-of-band system commands can trigger Claude’s prompt-injection defenses, which causes Claude to surface the text to you instead of treating it as context.Claude Code saves the injected text in the session transcript. For mid-session events like `PostToolUse` or `UserPromptSubmit`, when you resume with `--continue` or `--resume`, Claude Code replays the saved text rather than re-running the hook for past turns, so values like timestamps or commit SHAs become stale. `SessionStart` hooks run again on resume with `source` set to `"resume"`, or `"fork"` if you added `--fork-session`, so they can refresh their context.
|
|
731
959
|
|
|
732
960
|
#### [](https://code.claude.com/docs/en/hooks\#decision-control) Decision control
|
|
733
961
|
|
|
@@ -736,16 +964,18 @@ Not every event supports blocking or controlling behavior through JSON. The even
|
|
|
736
964
|
| Events | Decision pattern | Key fields |
|
|
737
965
|
| --- | --- | --- |
|
|
738
966
|
| UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact | Top-level `decision` | `decision: "block"`, `reason`. Stop and SubagentStop also accept `hookSpecificOutput.additionalContext` for [non-error feedback that continues the conversation](https://code.claude.com/docs/en/hooks#stop-decision-control) |
|
|
739
|
-
| TeammateIdle,
|
|
967
|
+
| TeammateIdle, 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; [TaskCompleted ignores it when the `TaskUpdate` tool triggered the event](https://code.claude.com/docs/en/hooks#taskcompleted-decision-control) |
|
|
968
|
+
| TaskCreated | Exit code or top-level `decision` | Exit code 2 or `decision: "block"` [cancels the task](https://code.claude.com/docs/en/hooks#taskcreated-decision-control) and returns the message to Claude. `continue: false` is ignored |
|
|
740
969
|
| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask/defer), `permissionDecisionReason` |
|
|
970
|
+
| PreModelSwitch | `hookSpecificOutput` or top-level `decision` | `permissionDecision` (allow/deny/ask), `permissionDecisionReason`. `decision: "block"` also [cancels the switch](https://code.claude.com/docs/en/hooks#premodelswitch-decision-control) |
|
|
741
971
|
| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |
|
|
742
|
-
| PermissionDenied | `hookSpecificOutput` | `retry: true` tells the model it may retry the denied tool call |
|
|
972
|
+
| PermissionDenied | `hookSpecificOutput` | `retry: true` tells the model it may retry the denied tool call; Claude Code ignores it for [no-verdict denials](https://code.claude.com/docs/en/hooks#permissiondenied-decision-control) |
|
|
743
973
|
| WorktreeCreate | path return | Command hook prints path on stdout; HTTP hook returns `hookSpecificOutput.worktreePath`. Hook failure or missing path fails creation |
|
|
744
|
-
|
|
|
745
|
-
| ElicitationResult | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values
|
|
974
|
+
| WorktreeRemove | Exit code | Any non-zero exit code makes the removal fail if the directory still exists afterward. JSON output is discarded |
|
|
975
|
+
| Elicitation, ElicitationResult | `hookSpecificOutput` or top-level `decision` | `action` (accept/decline/cancel), `content` (form field values). `decision: "block"` also [declines](https://code.claude.com/docs/en/hooks#other-ways-to-decline-an-elicitation) |
|
|
746
976
|
| MessageDisplay | `hookSpecificOutput` | `displayContent` replaces the displayed text on screen. Display-only: the transcript and what Claude sees keep the original |
|
|
747
|
-
| SessionStart,
|
|
748
|
-
|
|
|
977
|
+
| SessionStart, SubagentStart, PostModelSwitch | Context only | `hookSpecificOutput.additionalContext` adds context for Claude. SessionStart also accepts [`initialUserMessage`, `watchPaths`, `sessionTitle`, and `reloadSkills`](https://code.claude.com/docs/en/hooks#sessionstart-decision-control). No blocking or decision control |
|
|
978
|
+
| Setup, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, DirectoryAdded, FileChanged | None | No decision control. Used for side effects like logging or cleanup |
|
|
749
979
|
|
|
750
980
|
A few events can also rewrite content rather than only allow or block it:
|
|
751
981
|
|
|
@@ -763,7 +993,7 @@ For redaction or transformation use cases, intercept at `PreToolUse` for outboun
|
|
|
763
993
|
- PermissionRequest
|
|
764
994
|
|
|
765
995
|
|
|
766
|
-
|
|
996
|
+
The only value for `decision` is `"block"`. To allow the action to proceed, omit `decision` from your JSON, or exit 0 without any JSON at all:
|
|
767
997
|
|
|
768
998
|
```
|
|
769
999
|
{
|
|
@@ -808,7 +1038,7 @@ Each event corresponds to a point in Claude Code’s lifecycle where hooks can r
|
|
|
808
1038
|
|
|
809
1039
|
### [](https://code.claude.com/docs/en/hooks\#sessionstart) SessionStart
|
|
810
1040
|
|
|
811
|
-
Runs 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 doesn’t require a script, use [CLAUDE.md](https://code.claude.com/docs/en/memory) instead.SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` and `type: "mcp_tool"` hooks are supported.The matcher value corresponds to how the session was initiated:
|
|
1041
|
+
Runs 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 doesn’t require a script, use [CLAUDE.md](https://code.claude.com/docs/en/memory) instead.SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` and `type: "mcp_tool"` hooks are supported. See [MCP tool hook fields](https://code.claude.com/docs/en/hooks#mcp-tool-hook-fields) for when `mcp_tool` hooks run.The matcher value corresponds to how the session was initiated:
|
|
812
1042
|
|
|
813
1043
|
| Matcher | When it fires |
|
|
814
1044
|
| --- | --- |
|
|
@@ -816,6 +1046,9 @@ Runs when Claude Code starts a new session or resumes an existing session. Usefu
|
|
|
816
1046
|
| `resume` | `--resume`, `--continue`, or `/resume` |
|
|
817
1047
|
| `clear` | `/clear` |
|
|
818
1048
|
| `compact` | Auto or manual compaction |
|
|
1049
|
+
| `fork` | A new session forked from an existing one: `--fork-session` with `--resume` or `--continue`, the `/fork` background copy, `/branch`, or a conversation you [move to the background](https://code.claude.com/docs/en/agent-view#from-inside-a-session) |
|
|
1050
|
+
|
|
1051
|
+
Before v2.1.214, forked sessions reported source `"resume"`.When you start an interactive session, resume a conversation at launch with `--continue` or `--resume`, or run `/clear`, SessionStart hooks run in the background. You can type right away, and a conversation you resumed appears without waiting for the hooks. Claude’s first response still waits for the hooks to finish, so their context reaches Claude.When you switch conversations with `/resume` inside a session, the switch waits for the hooks to finish instead. If you run `/clear` or switch to another conversation while background hooks are still running, nothing they return applies to the session.The same wait applies at launch, including a resumed session: a prompt you send while SessionStart hooks are still running doesn’t reach Claude until they finish.During either wait, press `Esc` to take the prompt back into the input without sending it. The hooks keep running.
|
|
819
1052
|
|
|
820
1053
|
#### [](https://code.claude.com/docs/en/hooks\#sessionstart-input) SessionStart input
|
|
821
1054
|
|
|
@@ -823,10 +1056,21 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
823
1056
|
|
|
824
1057
|
| Field | Description |
|
|
825
1058
|
| --- | --- |
|
|
826
|
-
| `source` | How the session started: `"startup"` for new sessions, `"resume"` for resumed sessions, `"clear"` after `/clear`,
|
|
1059
|
+
| `source` | How the session started: `"startup"` for new sessions, `"resume"` for resumed sessions, `"clear"` after `/clear`, `"compact"` after compaction, or `"fork"` for a new session forked from an existing one |
|
|
827
1060
|
| `model` | The active model identifier. It can be omitted, for example after `/clear` or when a session is restored through conversation recovery, so check for the field before reading it |
|
|
828
1061
|
| `agent_type` | The agent name, present when you start Claude Code with `claude --agent <name>` |
|
|
829
|
-
| `session_title` | The
|
|
1062
|
+
| `session_title` | The session’s custom title, present when one is set, for example with `--name`, `/rename`, a hook’s `sessionTitle` output, or the Agent SDK’s `renameSession()`. A hook that emits `sessionTitle` can check this field first to avoid overwriting an existing custom title |
|
|
1063
|
+
|
|
1064
|
+
A session you haven’t named can still have a [generated title](https://code.claude.com/docs/en/sessions#name-your-sessions). That title isn’t a custom title and doesn’t appear in `session_title`.When `source` is `"resume"` or `"fork"` and the transcript contains at least one response from Claude, SessionStart hooks also receive the four fields below. Your hook can use them to report what resuming a stale conversation costs before the first request, for example in a [`systemMessage`](https://code.claude.com/docs/en/hooks#json-output). These fields require Claude Code v2.1.251 or later.
|
|
1065
|
+
|
|
1066
|
+
| Field | Description |
|
|
1067
|
+
| --- | --- |
|
|
1068
|
+
| `seconds_since_last_response` | Wall-clock seconds since the last response in the resumed transcript |
|
|
1069
|
+
| `context_tokens` | Tokens the first request of the resumed session re-sends as its prompt |
|
|
1070
|
+
| `prompt_cache_likely_expired` | `true` when the last response is older than the session’s [prompt cache lifetime](https://code.claude.com/docs/en/prompt-caching#cache-lifetime) or a later compaction replaced the cached conversation |
|
|
1071
|
+
| `estimated_cache_write_usd` | Estimated cost in US dollars of writing `context_tokens` to the prompt cache on the session’s model, excluding the response |
|
|
1072
|
+
|
|
1073
|
+
This example shows the input for a session resumed 90 minutes after its last response:
|
|
830
1074
|
|
|
831
1075
|
```
|
|
832
1076
|
{
|
|
@@ -834,22 +1078,28 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
834
1078
|
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
|
|
835
1079
|
"cwd": "/Users/...",
|
|
836
1080
|
"hook_event_name": "SessionStart",
|
|
837
|
-
"source": "
|
|
838
|
-
"model": "claude-
|
|
1081
|
+
"source": "resume",
|
|
1082
|
+
"model": "claude-opus-5",
|
|
1083
|
+
"seconds_since_last_response": 5400,
|
|
1084
|
+
"context_tokens": 182340,
|
|
1085
|
+
"prompt_cache_likely_expired": true,
|
|
1086
|
+
"estimated_cache_write_usd": 1.1396
|
|
839
1087
|
}
|
|
840
1088
|
```
|
|
841
1089
|
|
|
842
1090
|
#### [](https://code.claude.com/docs/en/hooks\#sessionstart-decision-control) SessionStart decision control
|
|
843
1091
|
|
|
844
|
-
|
|
1092
|
+
A SessionStart hook can add context for Claude, supply the first user message, set the session title, watch files, and reload skills. Return the field for each one, in addition to the [JSON output fields](https://code.claude.com/docs/en/hooks#json-output) available to all hooks:
|
|
845
1093
|
|
|
846
1094
|
| Field | Description |
|
|
847
1095
|
| --- | --- |
|
|
848
1096
|
| `additionalContext` | String added to Claude’s context at the start of the conversation, before the first prompt. See [Add context for Claude](https://code.claude.com/docs/en/hooks#add-context-for-claude) for how the text is delivered and what to put in it |
|
|
849
|
-
| `initialUserMessage` | String used as the first user message of the session
|
|
850
|
-
| `sessionTitle` | Sets the session title, with the same effect as `/rename`.
|
|
1097
|
+
| `initialUserMessage` | String used as the first user message of the session, in [non-interactive mode](https://code.claude.com/docs/en/headless) with the `-p` flag. It becomes the first turn even if you pass no prompt. A prompt you do pass follows as the next turn |
|
|
1098
|
+
| `sessionTitle` | Sets the session title, with the same effect as `/rename`. Applies when `source` is `"startup"`, `"resume"`, or `"fork"` |
|
|
851
1099
|
| `watchPaths` | Array of absolute paths to watch for [FileChanged](https://code.claude.com/docs/en/hooks#filechanged) events during this session |
|
|
852
|
-
| `reloadSkills` | Boolean. When `true`, Claude Code re-scans the [skill](https://code.claude.com/docs/en/skills) and command directories after the SessionStart hooks complete
|
|
1100
|
+
| `reloadSkills` | Boolean. When `true`, Claude Code re-scans the [skill](https://code.claude.com/docs/en/skills) and command directories after the SessionStart hooks complete. See [Reload skills that a hook installs](https://code.claude.com/docs/en/hooks#reload-skills-that-a-hook-installs) |
|
|
1101
|
+
|
|
1102
|
+
This output adds context and names the session:
|
|
853
1103
|
|
|
854
1104
|
```
|
|
855
1105
|
{
|
|
@@ -861,7 +1111,11 @@ Any text your hook script prints to stdout is added as context for Claude. In ad
|
|
|
861
1111
|
}
|
|
862
1112
|
```
|
|
863
1113
|
|
|
864
|
-
|
|
1114
|
+
A hook that only adds context can print it without building JSON, because Claude Code adds a SessionStart hook’s [plain-text stdout](https://code.claude.com/docs/en/hooks#exit-code-0) to Claude’s context.If your plugin’s SessionStart hook supplies `initialUserMessage` or `sessionTitle`, install the plugin before the session starts. Claude Code ignores both fields from a plugin that finishes installing after the SessionStart hooks have run.
|
|
1115
|
+
|
|
1116
|
+
#### [](https://code.claude.com/docs/en/hooks\#reload-skills-that-a-hook-installs) Reload skills that a hook installs
|
|
1117
|
+
|
|
1118
|
+
To make skills that a SessionStart hook installs available in the same session, return `reloadSkills`. Skill discovery normally runs before SessionStart hooks finish, so without it, files a hook writes into `~/.claude/skills/` or `.claude/skills/` can be missing when the first prompt runs.This example syncs a shared skills repository and requests the re-scan:
|
|
865
1119
|
|
|
866
1120
|
```
|
|
867
1121
|
#!/bin/bash
|
|
@@ -872,9 +1126,11 @@ git -C ~/.claude/skills/team-skills pull --quiet 2>/dev/null || \
|
|
|
872
1126
|
echo '{"hookSpecificOutput": {"hookEventName": "SessionStart", "reloadSkills": true}}'
|
|
873
1127
|
```
|
|
874
1128
|
|
|
1129
|
+
The repository URL is a placeholder. Replace it with your own skills repository.
|
|
1130
|
+
|
|
875
1131
|
#### [](https://code.claude.com/docs/en/hooks\#persist-environment-variables) Persist environment variables
|
|
876
1132
|
|
|
877
|
-
SessionStart hooks have access to the `CLAUDE_ENV_FILE` environment variable, which provides a file path where you can persist environment variables for
|
|
1133
|
+
SessionStart hooks have access to the `CLAUDE_ENV_FILE` environment variable, which provides a file path where you can persist environment variables for the shell commands Claude runs later in the session.To set individual environment variables, write `export` statements to `CLAUDE_ENV_FILE`. Use append (`>>`) to preserve variables set by other hooks:
|
|
878
1134
|
|
|
879
1135
|
```
|
|
880
1136
|
#!/bin/bash
|
|
@@ -907,9 +1163,18 @@ fi
|
|
|
907
1163
|
exit 0
|
|
908
1164
|
```
|
|
909
1165
|
|
|
910
|
-
|
|
1166
|
+
Each Bash command runs the file’s contents as shell code before the command itself, so a line there can use anything Bash evaluates, such as the `$PATH` reference in `export PATH="$PATH:./node_modules/.bin"`.
|
|
1167
|
+
|
|
1168
|
+
##### Persisted variables in PowerShell commands
|
|
911
1169
|
|
|
912
|
-
|
|
1170
|
+
[PowerShell](https://code.claude.com/docs/en/tools-reference#powershell-tool) commands receive the variables from `CLAUDE_ENV_FILE` too, in Claude Code v2.1.296 or later, but PowerShell never runs the file. Claude Code reads the assignments out of it and copies them into the PowerShell command’s environment instead. It does so only when every line is one of the following, across what every hook in this session wrote and any script you [set `CLAUDE_ENV_FILE` to](https://code.claude.com/docs/en/env-vars) before launch:
|
|
1171
|
+
|
|
1172
|
+
- A blank line or a `#` comment
|
|
1173
|
+
- One assignment at the start of the line, written `export NAME=value`, `declare -x NAME=value`, or `NAME=value`, with a value Bash would use exactly as written, made of any mix of these parts: unquoted text that uses only letters, digits, and the characters `_ @ % + = : , . / -`, text in single quotes, and text in double quotes where any `$`, backtick, or `"` inside is escaped with a backslash
|
|
1174
|
+
|
|
1175
|
+
If any line is of another kind, such as `export PATH="$PATH:./node_modules/.bin"` with its unescaped `$PATH`, a `source` command, or the `$'...'` strings that `direnv export bash` prints, PowerShell commands receive none of the variables, and `claude --debug` logs `Session environment is not all plain assignments`. Bash commands still receive all of them. On Windows, PowerShell commands also don’t receive a variable whose value contains `/` or `\`, because Git Bash and Windows write paths differently. A [sandboxed](https://code.claude.com/docs/en/sandboxing) PowerShell command receives none of the variables.
|
|
1176
|
+
|
|
1177
|
+
`CLAUDE_ENV_FILE` is available for SessionStart, [Setup](https://code.claude.com/docs/en/hooks#setup), [CwdChanged](https://code.claude.com/docs/en/hooks#cwdchanged), and [FileChanged](https://code.claude.com/docs/en/hooks#filechanged) hooks. Other hook events don’t have access to this variable, and neither does a hook that runs in PowerShell, whether through [`"shell": "powershell"`](https://code.claude.com/docs/en/hooks#command-hook-fields) or by default on Windows without Git Bash.
|
|
913
1178
|
|
|
914
1179
|
### [](https://code.claude.com/docs/en/hooks\#setup) Setup
|
|
915
1180
|
|
|
@@ -920,7 +1185,7 @@ Fires only when you launch Claude Code with `--init-only`, or with `--init` or `
|
|
|
920
1185
|
| `init` | `claude --init-only` or `claude -p --init` |
|
|
921
1186
|
| `maintenance` | `claude -p --maintenance` |
|
|
922
1187
|
|
|
923
|
-
|
|
1188
|
+
When you run `claude --init-only`, Claude Code runs Setup hooks and `SessionStart` hooks with the `startup` matcher, then exits without starting a conversation.When you start or continue a conversation with `-p`, you also need to supply a prompt, as an argument or piped on stdin. You can skip the prompt when a `SessionStart` hook supplies [`initialUserMessage`](https://code.claude.com/docs/en/hooks#sessionstart-decision-control) or when you resume a session with a [deferred tool call](https://code.claude.com/docs/en/hooks#defer-a-tool-call-for-later).On success, `--init-only` prints nothing to the terminal. To confirm the hooks ran, start with `claude --debug-file <path> --init-only`, replacing `<path>` with a log file location, and check the log for the Setup and SessionStart hook entries.Because Setup doesn’t fire on every launch, a plugin that needs a dependency installed can’t rely on Setup alone. The practical pattern is to check for the dependency on first use and install on miss, for example a hook or skill that tests for `${CLAUDE_PLUGIN_DATA}/node_modules` and runs `npm install` if absent. See the [persistent data directory](https://code.claude.com/docs/en/plugins/components#path-variables-and-persistent-data) for where to store installed dependencies. If you distribute your plugin through a marketplace, you may not need this pattern: Claude Code [installs eligible Node.js package dependencies automatically](https://code.claude.com/docs/en/plugins/loading#node-js-package-dependencies) when it caches the plugin.
|
|
924
1189
|
|
|
925
1190
|
#### [](https://code.claude.com/docs/en/hooks\#setup-input) Setup input
|
|
926
1191
|
|
|
@@ -938,26 +1203,11 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
938
1203
|
|
|
939
1204
|
#### [](https://code.claude.com/docs/en/hooks\#setup-decision-control) Setup decision control
|
|
940
1205
|
|
|
941
|
-
Setup hooks can’t block.
|
|
942
|
-
|
|
943
|
-
| Field | Description |
|
|
944
|
-
| --- | --- |
|
|
945
|
-
| `additionalContext` | String added to Claude’s context. Multiple hooks’ values are concatenated |
|
|
946
|
-
|
|
947
|
-
```
|
|
948
|
-
{
|
|
949
|
-
"hookSpecificOutput": {
|
|
950
|
-
"hookEventName": "Setup",
|
|
951
|
-
"additionalContext": "Dependencies installed: node_modules, .venv"
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
```
|
|
955
|
-
|
|
956
|
-
Setup 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](https://code.claude.com/docs/en/hooks#persist-environment-variables). Only `type: "command"` and `type: "mcp_tool"` hooks are supported.
|
|
1206
|
+
Setup hooks can’t block; execution continues on any exit code. On every exit code, Claude Code discards a Setup hook’s [JSON output fields](https://code.claude.com/docs/en/hooks#json-output), such as `systemMessage`, `continue`, and `hookSpecificOutput.additionalContext`. With `-p`, a Setup hook’s stdout, stderr, and exit code appear in the run’s output only as [`hook_response` events](https://code.claude.com/docs/en/headless#read-session-metadata) when you launch with `--output-format stream-json --verbose`.Setup hooks have access to `CLAUDE_ENV_FILE`. Variables written to that file persist into subsequent Bash commands for the session, as in [SessionStart hooks](https://code.claude.com/docs/en/hooks#persist-environment-variables). Only `type: "command"` hooks run on `Setup`. A `type: "mcp_tool"` hook on `Setup` is always skipped, as described under [MCP tool hook fields](https://code.claude.com/docs/en/hooks#mcp-tool-hook-fields).
|
|
957
1207
|
|
|
958
1208
|
### [](https://code.claude.com/docs/en/hooks\#instructionsloaded) InstructionsLoaded
|
|
959
1209
|
|
|
960
|
-
Fires when a `CLAUDE.md` or `.claude/rules/*.md` file is loaded into context. This event fires at session start for eagerly
|
|
1210
|
+
Fires 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 doesn’t support blocking or decision control. It runs asynchronously for observability purposes.This event doesn’t fire when Claude [reads `AGENTS.md` directly](https://code.claude.com/docs/en/memory#agents-md) through the **Project instructions** setting. It does fire when a `CLAUDE.md` imports your `AGENTS.md`, with `load_reason` set to `include` as for any other imported file, and when `CLAUDE.md` is a symlink to it, as a normal `CLAUDE.md` load.The 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.
|
|
961
1211
|
|
|
962
1212
|
#### [](https://code.claude.com/docs/en/hooks\#instructionsloaded-input) InstructionsLoaded input
|
|
963
1213
|
|
|
@@ -986,17 +1236,23 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
986
1236
|
|
|
987
1237
|
#### [](https://code.claude.com/docs/en/hooks\#instructionsloaded-decision-control) InstructionsLoaded decision control
|
|
988
1238
|
|
|
989
|
-
InstructionsLoaded hooks have no decision control. They can’t block or modify instruction loading. Use this event for audit logging, compliance tracking, or observability.
|
|
1239
|
+
InstructionsLoaded hooks have no decision control. They can’t block or modify instruction loading. Claude Code discards their [JSON output fields](https://code.claude.com/docs/en/hooks#json-output), such as `systemMessage` and `continue`. Use this event for audit logging, compliance tracking, or observability.
|
|
990
1240
|
|
|
991
1241
|
### [](https://code.claude.com/docs/en/hooks\#userpromptsubmit) UserPromptSubmit
|
|
992
1242
|
|
|
993
|
-
Runs when
|
|
1243
|
+
Runs when a prompt is submitted, before Claude processes it. This allows you
|
|
994
1244
|
to add additional context based on the prompt/conversation, validate prompts, or
|
|
995
|
-
block certain types of prompts.`UserPromptSubmit` hooks
|
|
1245
|
+
block certain types of prompts.`UserPromptSubmit` hooks don’t fire only on prompts you type. Claude Code also runs them on:
|
|
1246
|
+
|
|
1247
|
+
- A [scheduled task](https://code.claude.com/docs/en/scheduled-tasks) firing, including a `/loop` iteration
|
|
1248
|
+
- A [background subagent](https://code.claude.com/docs/en/sub-agents#run-subagents-in-foreground-or-background) reporting back to the session that started it
|
|
1249
|
+
- A [message another session sends](https://code.claude.com/docs/en/cross-session-messaging) to your main conversation
|
|
1250
|
+
|
|
1251
|
+
`UserPromptSubmit` hooks have a default timeout of 30 seconds for `command`, `http`, and `mcp_tool` types, shorter than the 600-second default for those types on most other events. Because this hook runs before every prompt and blocks model processing until it completes, a stuck hook stalls the session. If your hook needs more time, set the `timeout` field in the hook entry.Apart from a command hook you run with [`async: true`](https://code.claude.com/docs/en/hooks#run-hooks-in-the-background), a `UserPromptSubmit` command, HTTP, or MCP tool hook that reaches its timeout is canceled and its output, including any `additionalContext`, is discarded. The prompt still reaches Claude without that context. To block the prompt instead, set [`onFailure: "block"`](https://code.claude.com/docs/en/hooks#block-the-action-when-a-hook-fails) on a command or HTTP hook. The transcript shows a notice naming the hook, the timeout that fired, and that the output was discarded.An [Agent SDK callback hook](https://code.claude.com/docs/en/agent-sdk/hooks) on `UserPromptSubmit` that reaches its timeout blocks the prompt with a message naming the hook and the timeout, because a callback there can be acting as a policy gate that must not fail open. The session continues. Before v2.1.208, a callback timeout on that event ended the turn with an execution error.
|
|
996
1252
|
|
|
997
1253
|
#### [](https://code.claude.com/docs/en/hooks\#userpromptsubmit-input) UserPromptSubmit input
|
|
998
1254
|
|
|
999
|
-
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields), UserPromptSubmit hooks receive the `prompt` field containing the text the
|
|
1255
|
+
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields), UserPromptSubmit hooks receive the `prompt` field containing the submitted text. Pasted content that collapsed to a `[Pasted text #N]` placeholder arrives expanded in place. In sessions where Claude Code [marks pasted text for Claude](https://code.claude.com/docs/en/terminal-config#how-claude-treats-pasted-text), that expanded content sits between a `<pasted_content id="…">` line and a `</pasted_content id="…">` line, so account for those lines if your hook parses the prompt.UserPromptSubmit hooks also receive `session_title` when the session has a custom title, with the same meaning as the [SessionStart `session_title` field](https://code.claude.com/docs/en/hooks#sessionstart-input).
|
|
1000
1256
|
|
|
1001
1257
|
```
|
|
1002
1258
|
{
|
|
@@ -1011,20 +1267,22 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
1011
1267
|
|
|
1012
1268
|
#### [](https://code.claude.com/docs/en/hooks\#userpromptsubmit-decision-control) UserPromptSubmit decision control
|
|
1013
1269
|
|
|
1014
|
-
`UserPromptSubmit` hooks can control whether a
|
|
1270
|
+
`UserPromptSubmit` hooks can control whether a submitted prompt is processed and add context. All [JSON output fields](https://code.claude.com/docs/en/hooks#json-output) are available.There are two ways to add context to the conversation on exit code 0:
|
|
1015
1271
|
|
|
1016
|
-
- **Plain text stdout**:
|
|
1272
|
+
- **Plain text stdout**: Claude Code adds stdout it [treats as plain text](https://code.claude.com/docs/en/hooks#exit-code-0) to Claude’s context
|
|
1017
1273
|
- **JSON with `additionalContext`**: use the JSON format below for more control. The `additionalContext` field is added as context
|
|
1018
1274
|
|
|
1019
|
-
|
|
1275
|
+
Neither channel produces a visible transcript entry. Plain stdout and the `additionalContext` value are each injected as a system reminder that starts with the hook’s name; Claude reads both. To confirm delivery, check the [debug log](https://code.claude.com/docs/en/hooks#debug-hooks).To block a prompt, return a JSON object with `decision` set to `"block"`:
|
|
1020
1276
|
|
|
1021
1277
|
| Field | Description |
|
|
1022
1278
|
| --- | --- |
|
|
1023
|
-
| `decision` | `"block"`
|
|
1279
|
+
| `decision` | `"block"` stops the prompt before it reaches Claude. Omit to allow the prompt to proceed |
|
|
1024
1280
|
| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |
|
|
1025
1281
|
| `additionalContext` | String added to Claude’s context alongside the submitted prompt. See [Add context for Claude](https://code.claude.com/docs/en/hooks#add-context-for-claude) |
|
|
1026
1282
|
| `sessionTitle` | Sets the session title. Use to name sessions automatically based on the prompt content |
|
|
1027
|
-
| `suppressOriginalPrompt` | If `true` when
|
|
1283
|
+
| `suppressOriginalPrompt` | If `true` when the hook blocks the prompt, leaves the prompt text out of the block message. See [What a blocked prompt leaves behind](https://code.claude.com/docs/en/hooks#what-a-blocked-prompt-leaves-behind) |
|
|
1284
|
+
|
|
1285
|
+
A hook that blocks by exiting 2 routes the same way as `reason`: the block message shows the stderr text to the user, and it isn’t added to context.
|
|
1028
1286
|
|
|
1029
1287
|
```
|
|
1030
1288
|
{
|
|
@@ -1033,11 +1291,16 @@ Plain stdout is shown as hook output in the transcript. The `additionalContext`
|
|
|
1033
1291
|
"hookSpecificOutput": {
|
|
1034
1292
|
"hookEventName": "UserPromptSubmit",
|
|
1035
1293
|
"additionalContext": "My additional context here",
|
|
1036
|
-
"sessionTitle": "My session title"
|
|
1294
|
+
"sessionTitle": "My session title",
|
|
1295
|
+
"suppressOriginalPrompt": true
|
|
1037
1296
|
}
|
|
1038
1297
|
}
|
|
1039
1298
|
```
|
|
1040
1299
|
|
|
1300
|
+
#### [](https://code.claude.com/docs/en/hooks\#what-a-blocked-prompt-leaves-behind) What a blocked prompt leaves behind
|
|
1301
|
+
|
|
1302
|
+
A blocked prompt never reaches Claude, but its text isn’t removed everywhere. By default the block message shown to the user ends with `Original prompt:` followed by the submitted text, and Claude Code writes that message to the session’s transcript file on disk. To leave the text out of the message, print JSON with `"suppressOriginalPrompt": true` inside `hookSpecificOutput`. This works whether the hook blocks with `decision: "block"` or by exiting 2.`suppressOriginalPrompt` changes only the block message. The submitted text can still appear in local files such as the session transcript and your prompt history, so a blocking hook isn’t a way to keep a secret off disk. To limit or remove those files, see [Plaintext storage](https://code.claude.com/docs/en/claude-directory#plaintext-storage) and [Clear local data](https://code.claude.com/docs/en/claude-directory#clear-local-data).
|
|
1303
|
+
|
|
1041
1304
|
### [](https://code.claude.com/docs/en/hooks\#userpromptexpansion) UserPromptExpansion
|
|
1042
1305
|
|
|
1043
1306
|
Runs when a user-typed command expands into a prompt before reaching Claude. Use this to block specific commands from direct invocation, inject context for a particular skill, or log which commands users invoke. For example, a hook matching `deploy` can block `/deploy` unless an approval file is present, or a hook matching a review skill can append the team’s review checklist as `additionalContext`.This event covers the path `PreToolUse` doesn’t: a `PreToolUse` hook matching the `Skill` tool fires only when Claude calls the tool, but typing `/skillname` directly bypasses `PreToolUse`. `UserPromptExpansion` fires on that direct path.Matches on `command_name`. Leave the matcher empty to fire on every prompt-type command.
|
|
@@ -1071,6 +1334,8 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
1071
1334
|
| `reason` | Shown to the user when `decision` is `"block"` |
|
|
1072
1335
|
| `additionalContext` | String added to Claude’s context alongside the expanded prompt. See [Add context for Claude](https://code.claude.com/docs/en/hooks#add-context-for-claude) |
|
|
1073
1336
|
|
|
1337
|
+
A hook that blocks by exiting 2 routes the same way as `reason`: the block message shows the stderr text to the user.
|
|
1338
|
+
|
|
1074
1339
|
```
|
|
1075
1340
|
{
|
|
1076
1341
|
"decision": "block",
|
|
@@ -1126,7 +1391,7 @@ In addition to the [JSON output fields](https://code.claude.com/docs/en/hooks#js
|
|
|
1126
1391
|
| --- | --- |
|
|
1127
1392
|
| `displayContent` | Text displayed in place of the delta. Omit it to display the original |
|
|
1128
1393
|
|
|
1129
|
-
MessageDisplay hooks have no decision control. They can’t block the message or change what is stored in the transcript or sent to Claude.This example strips markdown formatting from Claude’s responses for a plain-text display. The script reads each batch from stdin, removes bold markers and inline code backticks from `delta`, and returns the result as `displayContent`.
|
|
1394
|
+
MessageDisplay hooks have no decision control. They can’t block the message or change what is stored in the transcript or sent to Claude. Claude Code acts on `displayContent` from their JSON output and discards `systemMessage` and `continue`.This example strips markdown formatting from Claude’s responses for a plain-text display. The script reads each batch from stdin, removes bold markers and inline code backticks from `delta`, and returns the result as `displayContent`.
|
|
1130
1395
|
|
|
1131
1396
|
- macOS/Linux
|
|
1132
1397
|
|
|
@@ -1160,8 +1425,6 @@ Save this script to `.claude/hooks/plain-display.sh` in your project and make it
|
|
|
1160
1425
|
jq '{hookSpecificOutput: {hookEventName: "MessageDisplay", displayContent: (.delta | gsub("\\*\\*"; "") | gsub("`"; ""))}}'
|
|
1161
1426
|
```
|
|
1162
1427
|
|
|
1163
|
-
The script needs `jq` on your `PATH`.
|
|
1164
|
-
|
|
1165
1428
|
Register a command hook that runs the script through PowerShell:
|
|
1166
1429
|
|
|
1167
1430
|
```
|
|
@@ -1205,15 +1468,36 @@ Batches with no markdown pass through unchanged. If the script fails, for exampl
|
|
|
1205
1468
|
|
|
1206
1469
|
### [](https://code.claude.com/docs/en/hooks\#pretooluse) PreToolUse
|
|
1207
1470
|
|
|
1208
|
-
Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name
|
|
1471
|
+
Runs after Claude creates tool parameters and before processing the tool call. Matches on any tool name except `EndConversation`: built-in tools such as `Bash`, `PowerShell`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Agent`, `Workflow`, `WebFetch`, `WebSearch`, `AskUserQuestion`, and `ExitPlanMode`, and any [MCP tool names](https://code.claude.com/docs/en/hooks#match-mcp-tools).To run a hook when a specific file changes on disk, whatever wrote it, use [FileChanged](https://code.claude.com/docs/en/hooks#filechanged) instead of matching file-editing tools by name. Unlike PreToolUse, Claude Code runs FileChanged hooks after the change, and they have no decision control, so they can’t block the write.
|
|
1209
1472
|
|
|
1210
|
-
PreToolUse runs only when Claude calls a tool. Files you [reference with `@` in your prompt](https://code.claude.com/docs/en/common-workflows#reference-files-and-directories) are added without any tool call: Claude Code inserts their contents while building the prompt, so no PreToolUse hook fires for them, including hooks matching `Read`. To block specific paths from `@` references, use a [`Read` deny rule](https://code.claude.com/docs/en/permissions#read-and-edit) instead.
|
|
1473
|
+
PreToolUse runs only when Claude calls a tool. Files you [reference with `@` in your prompt](https://code.claude.com/docs/en/common-workflows#reference-files-and-directories) are added without any tool call: Claude Code inserts their contents while building the prompt, so no PreToolUse hook fires for them, including hooks matching `Read`. To block specific paths from `@` references, use a [`Read` deny rule](https://code.claude.com/docs/en/permissions#read-and-edit) instead.PreToolUse also doesn’t fire for [`EndConversation`](https://code.claude.com/docs/en/tools-reference#endconversation-tool-behavior).
|
|
1211
1474
|
|
|
1212
|
-
Use [PreToolUse decision control](https://code.claude.com/docs/en/hooks#pretooluse-decision-control) to allow, deny, ask, or defer the tool call.
|
|
1475
|
+
Use [PreToolUse decision control](https://code.claude.com/docs/en/hooks#pretooluse-decision-control) to allow, deny, ask, or defer the tool call.An [Agent SDK callback hook](https://code.claude.com/docs/en/agent-sdk/hooks) on `PreToolUse` that exceeds its timeout blocks the tool call, and Claude receives an error result naming the timeout. An explicit deny returned by another hook still takes precedence.
|
|
1213
1476
|
|
|
1214
1477
|
#### [](https://code.claude.com/docs/en/hooks\#pretooluse-input) PreToolUse input
|
|
1215
1478
|
|
|
1216
|
-
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields), PreToolUse hooks receive `tool_name`, `tool_input`, and `tool_use_id`. The `
|
|
1479
|
+
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields), PreToolUse hooks receive `tool_name`, `tool_input`, and `tool_use_id`.For an [MCP tool](https://code.claude.com/docs/en/hooks#match-mcp-tools), the input also carries `mcp_server`, an object with the server’s `name` and a `source` that says where the server’s definition came from. The `source` values include `plugin`, `sdk`, and configuration scopes such as `user` and `project`. [`McpServerProvenance`](https://code.claude.com/docs/en/agent-sdk/typescript#mcpserverprovenance) in the Agent SDK reference lists them all and says how to treat one you don’t recognize. Base trust decisions on `source` rather than on `name` or the `mcp__<server>__` tool-name prefix. The `mcp_server` field requires Claude Code v2.1.274 or later.For the file tools `Write`, `Edit`, and `Read`, `tool_input.file_path` is always absolute:
|
|
1480
|
+
|
|
1481
|
+
- Claude Code expands `~` and relative paths before hooks run, so a hook that matches on paths can’t be bypassed via `~` or a relative spelling of the same path
|
|
1482
|
+
- On Windows, the path arrives with backslash separators, even when your hook runs under Git Bash where `$PWD` looks like `/c/project`
|
|
1483
|
+
- A comparison written with forward slashes, such as a `/src/` check, never matches a backslash path, and the tool call proceeds as if the hook had nothing to block
|
|
1484
|
+
- Normalize separators before comparing: `FILE_PATH="${FILE_PATH//\\//}"` in Bash, or `file_path.replace("\\", "/")` in Python, then match a path segment such as `/src/` rather than anchoring with `^`, since the path is absolute
|
|
1485
|
+
|
|
1486
|
+
A `Write` call on Windows delivers:
|
|
1487
|
+
|
|
1488
|
+
```
|
|
1489
|
+
{
|
|
1490
|
+
"hook_event_name": "PreToolUse",
|
|
1491
|
+
"tool_name": "Write",
|
|
1492
|
+
"tool_input": {
|
|
1493
|
+
"file_path": "C:\\project\\src\\index.ts",
|
|
1494
|
+
"content": "..."
|
|
1495
|
+
},
|
|
1496
|
+
...
|
|
1497
|
+
}
|
|
1498
|
+
```
|
|
1499
|
+
|
|
1500
|
+
The `tool_input` fields depend on the tool:
|
|
1217
1501
|
|
|
1218
1502
|
##### Bash
|
|
1219
1503
|
|
|
@@ -1223,9 +1507,41 @@ Executes shell commands.
|
|
|
1223
1507
|
| --- | --- | --- | --- |
|
|
1224
1508
|
| `command` | string | `"npm test"` | The shell command to execute |
|
|
1225
1509
|
| `description` | string | `"Run test suite"` | Optional description of what the command does |
|
|
1510
|
+
| `timeout` | number | `120000` | Optional timeout in milliseconds. Values above the [maximum](https://code.claude.com/docs/en/tools-reference#bash-tool-behavior) are reduced to the maximum rather than rejected |
|
|
1511
|
+
| `run_in_background` | boolean | `false` | Whether to run the command in background |
|
|
1512
|
+
|
|
1513
|
+
When a Bash command changes files in a Git repository, Claude Code can record what changed. It records the changes in every permission mode when the [`bashEditDiffEnabled`](https://code.claude.com/docs/en/settings-reference#basheditdiffenabled) setting turns recording on; that setting’s entry says which files can set it. Otherwise it records them only in auto mode and `bypassPermissions` mode, and only when Claude Code directs Claude to edit files through Bash. Set `bashEditDiffEnabled` to `false` to turn the recording off. Background commands and read-only commands carry no diff.Your [PostToolUse hook](https://code.claude.com/docs/en/hooks#posttooluse) then receives the changed files in `tool_response.bashEditDiff`. The list covers what changed under the repository while the command ran. Files that Git ignores and files in submodules aren’t listed. Requires Claude Code v2.1.269 or later.
|
|
1514
|
+
|
|
1515
|
+
The list is best effort and in public beta. Claude Code can miss a change, include a file that another process changed at the same time, or stop at its size limits. The field shape may change. Use the list to find what to review, not to enforce a policy.
|
|
1516
|
+
|
|
1517
|
+
`changedFiles` and `files` list what the command changed; the remaining fields say how complete and how reliable that list is.
|
|
1518
|
+
|
|
1519
|
+
| Field | Type | Example | Description |
|
|
1520
|
+
| --- | --- | --- | --- |
|
|
1521
|
+
| `changedFiles` | array | `["/path/to/src/app.ts"]` | Absolute paths of the files the command changed, at most 200. Present whenever `files` holds a diff or `moreFiles` is above zero |
|
|
1522
|
+
| `files` | array | `[{"filePath": "/path/to/src/app.ts", "hunks": [...]}]` | Diffs of up to 5 changed files, for display. `created` or `deleted` is `true` for a file the command added or removed |
|
|
1523
|
+
| `moreFiles` | number | `2` | Count of changed files with no diff in `files` |
|
|
1524
|
+
| `unavailable` | boolean | `true` | Set when the diff is incomplete or couldn’t be taken |
|
|
1525
|
+
| `skipped` | boolean | `true` | Set for a Git command that moves the working tree, such as `git checkout` or `git stash`, so Claude Code takes no diff |
|
|
1526
|
+
| `shared` | boolean | `true` | Set when another Bash tool call, such as a subagent’s, ran in the same repository at the same time, so some listed changes may be that command’s |
|
|
1527
|
+
|
|
1528
|
+
##### PowerShell
|
|
1529
|
+
|
|
1530
|
+
Executes PowerShell commands. See the [PowerShell tool](https://code.claude.com/docs/en/tools-reference#powershell-tool) for availability by platform.The fields match the Bash tool, with the command string in `command`:
|
|
1531
|
+
|
|
1532
|
+
| Field | Type | Example | Description |
|
|
1533
|
+
| --- | --- | --- | --- |
|
|
1534
|
+
| `command` | string | `"Get-ChildItem -Recurse"` | The PowerShell command to execute |
|
|
1535
|
+
| `description` | string | `"List files recursively"` | Optional description of what the command does |
|
|
1226
1536
|
| `timeout` | number | `120000` | Optional timeout in milliseconds |
|
|
1227
1537
|
| `run_in_background` | boolean | `false` | Whether to run the command in background |
|
|
1228
1538
|
|
|
1539
|
+
Match `Bash|PowerShell` in hooks that inspect shell commands, so they cover both tools:
|
|
1540
|
+
|
|
1541
|
+
- On Windows, wherever the PowerShell tool is enabled, Claude treats PowerShell as the primary shell and routes shell commands through it.
|
|
1542
|
+
- On Windows without Git Bash, the tool is enabled automatically and Claude Code doesn’t register the Bash tool at all.
|
|
1543
|
+
- A hook that matches only `Bash` never fires there.
|
|
1544
|
+
|
|
1229
1545
|
##### Write
|
|
1230
1546
|
|
|
1231
1547
|
Creates or overwrites a file.
|
|
@@ -1286,6 +1602,7 @@ Fetches and processes web content.
|
|
|
1286
1602
|
| --- | --- | --- | --- |
|
|
1287
1603
|
| `url` | string | `"https://example.com/api"` | URL to fetch content from |
|
|
1288
1604
|
| `prompt` | string | `"Extract the API endpoints"` | Prompt to run on the fetched content |
|
|
1605
|
+
| `offset` | number | `100000` | Optional number of characters to skip from the start of the page. Claude sets it to keep reading a long page. Requires Claude Code v2.1.290 or later |
|
|
1289
1606
|
|
|
1290
1607
|
##### WebSearch
|
|
1291
1608
|
|
|
@@ -1308,20 +1625,21 @@ Spawns a [subagent](https://code.claude.com/docs/en/sub-agents).
|
|
|
1308
1625
|
| `subagent_type` | string | `"Explore"` | Type of specialized agent to use |
|
|
1309
1626
|
| `model` | string | `"sonnet"` | Optional model alias to override the default |
|
|
1310
1627
|
|
|
1311
|
-
|
|
1628
|
+
When a foreground Agent call completes, your [PostToolUse hook](https://code.claude.com/docs/en/hooks#posttooluse) receives the subagent’s result and run telemetry in `tool_response`. Read these fields to inspect the run; for token and cost rollups across subagents, use the [token and cost counters](https://code.claude.com/docs/en/monitoring-usage#token-counter) filtered to `query_source``"subagent"`, since `totalTokens` and `usage` cover the final request only:
|
|
1312
1629
|
|
|
1313
1630
|
| Field | Type | Example | Description |
|
|
1314
1631
|
| --- | --- | --- | --- |
|
|
1315
|
-
| `status` | string | `"completed"` | `"completed"` for foreground subagents, `"async_launched"` for background subagents.
|
|
1632
|
+
| `status` | string | `"completed"` | `"completed"` for foreground subagents, `"async_launched"` for background subagents. Subagents run in the background by default, so an Agent call that omits `run_in_background` also produces `"async_launched"` |
|
|
1316
1633
|
| `agentId` | string | `"a4d2c8f1e0b3a297"` | Identifier for the subagent run |
|
|
1317
|
-
| `content` | array | `[{"type": "text", "text": "Found 12 endpoints..."}]` | The subagent’s final text blocks |
|
|
1318
|
-
| `resolvedModel` | string | `"claude-sonnet-4-5"` | Model the subagent
|
|
1319
|
-
| `
|
|
1634
|
+
| `content` | array | `[{"type": "text", "text": "Found 12 endpoints..."}]` | The subagent’s final text blocks, or, for a subagent whose report goes through `SubagentHandback`, a short note about that hand-back in their place |
|
|
1635
|
+
| `resolvedModel` | string | `"claude-sonnet-4-5"` | Model the subagent started on, which may differ from the requested model |
|
|
1636
|
+
| `modelsUsed` | array | `["claude-sonnet-4-5", "claude-haiku-4-5"]` | Models used in order, with consecutive repeats collapsed; set only when the model was swapped mid-run. Requires Claude Code v2.1.212 or later |
|
|
1637
|
+
| `totalTokens` | number | `12450` | Token count from the subagent’s final API request: input, output, and cache tokens combined. This isn’t a total across the whole run |
|
|
1320
1638
|
| `totalDurationMs` | number | `48211` | Wall-clock duration of the subagent run |
|
|
1321
1639
|
| `totalToolUseCount` | number | `7` | Count of tool calls the subagent made |
|
|
1322
|
-
| `usage` | object | `{"input_tokens": 8320, ...}` | Per-type token breakdown: `input_tokens`, `output_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens` |
|
|
1640
|
+
| `usage` | object | `{"input_tokens": 8320, ...}` | Per-type token breakdown of the final API request: `input_tokens`, `output_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens` |
|
|
1323
1641
|
|
|
1324
|
-
For background subagents, the tool returns
|
|
1642
|
+
On Claude Code v2.1.271 or later, a subagent that runs with the [`SubagentHandback`](https://code.claude.com/docs/en/tools-reference) tool, which Claude Code provides in [auto mode](https://code.claude.com/docs/en/permission-modes#eliminate-prompts-with-auto-mode), delivers its report through that tool rather than returning it as text. The `content` field of its `completed` result then carries a short note about that hand-back rather than the report itself. To read the report, match a `PreToolUse` or `PostToolUse` hook on `SubagentHandback` and read `tool_input.message`.For background subagents, the tool returns when the task moves to the background, so `tool_response` carries no usage fields: a background launch returns immediately, and a foreground task that Claude Code backgrounds mid-run returns at that transition. It has `status: "async_launched"`, `agentId`, `description`, `prompt`, `outputFile`, and `resolvedModel`.On a `completed` response, `resolvedModel` names the model the subagent started on, which can differ from the `model` value in `tool_input`, such as when `availableModels` or another override applies. On an `async_launched` response, `resolvedModel` names the model in use when the agent moved to the background, so a swap that happened before backgrounding is reflected there. `modelsUsed` and the backgrounding-time `resolvedModel` behavior require Claude Code v2.1.212 or later.
|
|
1325
1643
|
|
|
1326
1644
|
##### AskUserQuestion
|
|
1327
1645
|
|
|
@@ -1329,7 +1647,7 @@ Asks the user one to four multiple-choice questions.
|
|
|
1329
1647
|
|
|
1330
1648
|
| Field | Type | Example | Description |
|
|
1331
1649
|
| --- | --- | --- | --- |
|
|
1332
|
-
| `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 |
|
|
1650
|
+
| `questions` | array | `[{"question": "Which framework?", "header": "Framework", "options": [{"label": "React", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}], "multiSelect": false}]` | Questions to present, each with a `question` string, short `header`, `options` array, and optional `multiSelect` flag |
|
|
1333
1651
|
| `answers` | object | `{"Which framework?": "React"}` | Optional. Maps question text to the selected option label. Multi-select answers join labels with commas. Claude doesn’t set this field; supply it via `updatedInput` to answer programmatically |
|
|
1334
1652
|
|
|
1335
1653
|
##### ExitPlanMode
|
|
@@ -1350,12 +1668,12 @@ In `PostToolUse`, `tool_response` is an object with `plan` and `filePath` fields
|
|
|
1350
1668
|
|
|
1351
1669
|
| Field | Description |
|
|
1352
1670
|
| --- | --- |
|
|
1353
|
-
| `permissionDecision` | `"allow"` skips the permission prompt, except for [
|
|
1354
|
-
| `permissionDecisionReason` | For `"
|
|
1355
|
-
| `updatedInput` | Modifies the tool’s input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones. Combine with `"allow"` to auto-approve, or `"ask"` to show the modified input to the user. For `"defer"`, ignored |
|
|
1671
|
+
| `permissionDecision` | `"allow"` skips the permission prompt, except for the [actions no mode auto-approves](https://code.claude.com/docs/en/permission-modes#actions-no-mode-auto-approves), for [reads from network paths](https://code.claude.com/docs/en/permissions#network-paths), and for `AskUserQuestion` and `ExitPlanMode`, which need [`updatedInput` paired with it](https://code.claude.com/docs/en/hooks#allow-with-updatedinput). `"deny"` prevents the tool call. `"ask"` prompts the user to confirm. `"defer"` exits gracefully so the tool can be resumed later. [Deny and ask rules](https://code.claude.com/docs/en/permissions#manage-permissions) are still evaluated regardless of what the hook returns |
|
|
1672
|
+
| `permissionDecisionReason` | For `"ask"`, shown to the user in the permission prompt. When Claude Code [denies the call](https://code.claude.com/docs/en/headless#turn-off-permission-prompts-in-unattended-runs) in a `-p` run where no one can answer that prompt, Claude reads the reason in the tool result instead. For `"deny"`, shown to Claude. For `"allow"` and `"defer"`, written to the [debug log](https://code.claude.com/docs/en/hooks#debug-hooks) only |
|
|
1673
|
+
| `updatedInput` | Modifies the tool’s input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones. Claude Code evaluates permission rules and a Bash command’s [auto-background eligibility](https://code.claude.com/docs/en/tools-reference#foreground-commands-that-move-to-the-background) against the input your hook returns, not the input Claude sent. Combine with `"allow"` to auto-approve, or `"ask"` to show the modified input to the user. For `"defer"`, ignored |
|
|
1356
1674
|
| `additionalContext` | String added to Claude’s context alongside the tool result. Ignored when `permissionDecision` is `"defer"`. See [Add context for Claude](https://code.claude.com/docs/en/hooks#add-context-for-claude) |
|
|
1357
1675
|
|
|
1358
|
-
When multiple PreToolUse hooks return different decisions, precedence is `deny` \> `defer` \> `ask` \> `allow`.When a hook returns `"ask"`, the permission prompt displayed to the user includes a label identifying where the hook came from: for
|
|
1676
|
+
When multiple PreToolUse hooks return different decisions, precedence is `deny` \> `defer` \> `ask` \> `allow`.A hook that blocks by exiting 2 routes the same way as `"deny"`: Claude sees the stderr message as the denial reason.When a hook returns `"ask"`, the permission prompt displayed to the user includes a label identifying where the hook came from: `[settings]` for a hook from any settings file or from agent frontmatter, `[plugin:<name>]` for a plugin’s hook, or `[skill]` for a hook from skill frontmatter. This helps users understand which configuration source is requesting confirmation.A hook’s `"ask"` also forces a permission prompt in [auto mode](https://code.claude.com/docs/en/permission-modes#eliminate-prompts-with-auto-mode): the classifier can still deny the tool call, but it can’t approve the call silently. Before v2.1.211, the classifier could approve a Bash command running outside the [sandbox](https://code.claude.com/docs/en/sandboxing) without showing the prompt the hook requested; the classifier still applied its own safety rules to that command, and a hook `"deny"` was always honored.
|
|
1359
1677
|
|
|
1360
1678
|
```
|
|
1361
1679
|
{
|
|
@@ -1371,22 +1689,48 @@ When multiple PreToolUse hooks return different decisions, precedence is `deny`
|
|
|
1371
1689
|
}
|
|
1372
1690
|
```
|
|
1373
1691
|
|
|
1374
|
-
`AskUserQuestion` and `ExitPlanMode` require user interaction and normally block in [non-interactive mode](https://code.claude.com/docs/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`](https://code.claude.com/docs/en/hooks#askuserquestion) object mapping each question’s text to the chosen answer.As of v2.1.199, an MCP tool whose server marks it with [`_meta["anthropic/requiresUserInteraction"]`](https://code.claude.com/docs/en/mcp#require-approval-for-a-specific-tool) is stricter: a hook can’t skip its approval prompt with `"allow"`, with or without `updatedInput`, because Claude Code can’t confirm the hook collected the interaction the tool needs.
|
|
1375
|
-
|
|
1376
1692
|
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.
|
|
1377
1693
|
|
|
1378
|
-
#### [](https://code.claude.com/docs/en/hooks\#
|
|
1694
|
+
#### [](https://code.claude.com/docs/en/hooks\#allow-with-updatedinput) Tools that require user interaction
|
|
1695
|
+
|
|
1696
|
+
`AskUserQuestion` and `ExitPlanMode` require user interaction. In [non-interactive mode](https://code.claude.com/docs/en/headless) with the `-p` flag, Claude Code offers them only when the run has a [permission host](https://code.claude.com/docs/en/headless#turn-off-permission-prompts-in-unattended-runs) to receive the prompt, such as an Agent SDK `canUseTool` callback.A `PreToolUse` hook satisfies that requirement when it does the following:
|
|
1697
|
+
|
|
1698
|
+
1. Reads the tool’s input from stdin
|
|
1699
|
+
2. Collects the answer through your own UI
|
|
1700
|
+
3. Returns `permissionDecision: "allow"` together with `updatedInput` that holds the answer, so the tool runs without prompting
|
|
1701
|
+
|
|
1702
|
+
Returning `"allow"` alone is not sufficient for these tools.For `AskUserQuestion`, echo back the original `questions` array and add an [`answers`](https://code.claude.com/docs/en/hooks#askuserquestion) object mapping each question’s text to the chosen answer. This output answers one question with `React`:
|
|
1703
|
+
|
|
1704
|
+
```
|
|
1705
|
+
{
|
|
1706
|
+
"hookSpecificOutput": {
|
|
1707
|
+
"hookEventName": "PreToolUse",
|
|
1708
|
+
"permissionDecision": "allow",
|
|
1709
|
+
"updatedInput": {
|
|
1710
|
+
"questions": [\
|
|
1711
|
+
{\
|
|
1712
|
+
"question": "Which framework?",\
|
|
1713
|
+
"header": "Framework",\
|
|
1714
|
+
"options": [{"label": "React", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}],\
|
|
1715
|
+
"multiSelect": false\
|
|
1716
|
+
}\
|
|
1717
|
+
],
|
|
1718
|
+
"answers": {"Which framework?": "React"}
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
```
|
|
1379
1723
|
|
|
1380
|
-
|
|
1724
|
+
An MCP tool whose server marks it with [`_meta["anthropic/requiresUserInteraction"]`](https://code.claude.com/docs/en/mcp#require-approval-for-a-specific-tool) is stricter: a hook can’t skip its approval prompt with `"allow"`, with or without `updatedInput`, because Claude Code can’t confirm the hook collected the interaction the tool needs.
|
|
1381
1725
|
|
|
1382
|
-
|
|
1726
|
+
#### [](https://code.claude.com/docs/en/hooks\#defer-a-tool-call-for-later) Defer a tool call for later
|
|
1383
1727
|
|
|
1384
|
-
The `AskUserQuestion` tool is the typical case: Claude wants to ask the user something, but there is no terminal to answer in. The round trip works like this:
|
|
1728
|
+
`"defer"` is for integrations that run `claude -p` as a subprocess and read its JSON output, such as an Agent SDK app or a custom UI built on top of Claude Code. It lets that calling process pause Claude at a tool call, collect input through its own interface, and resume where it left off. Claude Code honors this value only in [non-interactive mode](https://code.claude.com/docs/en/headless) with the `-p` flag. In interactive sessions it logs a warning and ignores the hook result.The `AskUserQuestion` tool is the typical case: Claude wants to ask the user something, but there is no terminal to answer in. A `-p` run offers `AskUserQuestion` only when it has a [permission host](https://code.claude.com/docs/en/headless#turn-off-permission-prompts-in-unattended-runs), such as an MCP tool you pass with `--permission-prompt-tool`, so start the run with one. The round trip works like this:
|
|
1385
1729
|
|
|
1386
1730
|
1. Claude calls `AskUserQuestion`. The `PreToolUse` hook fires.
|
|
1387
1731
|
2. The hook returns `permissionDecision: "defer"`. The tool doesn’t execute. The process exits with `stop_reason: "tool_deferred"` and the pending tool call preserved in the transcript.
|
|
1388
1732
|
3. The calling process reads `deferred_tool_use` from the SDK result, surfaces the question in its own UI, and waits for an answer.
|
|
1389
|
-
4. The calling process runs `claude -p --resume <session-id
|
|
1733
|
+
4. The calling process runs `claude -p --resume <session-id>` with the same permission host. The same tool call fires `PreToolUse` again.
|
|
1390
1734
|
5. The hook returns `permissionDecision: "allow"` with the answer in `updatedInput`. The tool executes and Claude continues.
|
|
1391
1735
|
|
|
1392
1736
|
The `deferred_tool_use` field carries the tool’s `id`, `name`, and `input`. The `input` is the parameters Claude generated for the tool call, captured before execution:
|
|
@@ -1400,23 +1744,23 @@ The `deferred_tool_use` field carries the tool’s `id`, `name`, and `input`. Th
|
|
|
1400
1744
|
"deferred_tool_use": {
|
|
1401
1745
|
"id": "toolu_01abc",
|
|
1402
1746
|
"name": "AskUserQuestion",
|
|
1403
|
-
"input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React"}, {"label": "Vue"}], "multiSelect": false }] }
|
|
1747
|
+
"input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}], "multiSelect": false }] }
|
|
1404
1748
|
}
|
|
1405
1749
|
}
|
|
1406
1750
|
```
|
|
1407
1751
|
|
|
1408
|
-
There is no timeout or retry limit. The session remains on disk until you resume it, subject to the [`cleanupPeriodDays`](https://code.claude.com/docs/en/settings#
|
|
1752
|
+
There is no timeout or retry limit. The session remains on disk until you resume it, subject to the [`cleanupPeriodDays`](https://code.claude.com/docs/en/settings-reference#cleanupperioddays) retention sweep, which deletes session files after 30 days by default, following the [retention sweep rules](https://code.claude.com/docs/en/claude-directory#cleaned-up-automatically). If the answer is not ready when you resume, the hook can return `"defer"` again and the process exits the same way. The calling process controls when to break the loop by eventually returning `"allow"` or `"deny"` from the hook.`"defer"` only works when Claude makes a single tool call in the turn. If Claude makes several tool calls at once, `"defer"` is ignored with a warning and the tool proceeds through the normal permission flow. The constraint exists because resume can only re-run one tool: there is no way to defer one call from a batch without leaving the others unresolved.If the deferred tool is no longer available when you resume, the process exits with `stop_reason: "tool_deferred_unavailable"` and `is_error: true` before the hook fires. This happens when an MCP server that provided the tool is not connected for the resumed session. The `deferred_tool_use` payload is still included so you can identify which tool went missing.
|
|
1409
1753
|
|
|
1410
|
-
|
|
1754
|
+
To resume a deferred session in plan mode, pass [`--permission-prompt-tool`](https://code.claude.com/docs/en/cli-reference#cli-flags) along with `--resume` so that Claude Code can present the plan for approval. For the other conditions, see [Resume in plan mode with `-p`](https://code.claude.com/docs/en/sessions#resume-in-plan-mode-with-p). Requires Claude Code v2.1.246 or later.When you resume with `-p`, Claude Code doesn’t restore any other stored permission mode. It starts the run in the permission mode a new `claude -p` run would start in, so pass `--permission-mode` or `--dangerously-skip-permissions` again if the deferred session used one. When you resume with `claude --resume <session-id>` without `-p`, Claude Code restores the stored permission mode, with the exceptions listed in [permission mode on resume](https://code.claude.com/docs/en/sessions#permission-mode-on-resume).
|
|
1411
1755
|
|
|
1412
1756
|
### [](https://code.claude.com/docs/en/hooks\#permissionrequest) PermissionRequest
|
|
1413
1757
|
|
|
1414
|
-
Runs when
|
|
1415
|
-
Use [PermissionRequest decision control](https://code.claude.com/docs/en/hooks#permissionrequest-decision-control) to allow or deny on behalf of the user.Matches on tool name, same values as PreToolUse.
|
|
1758
|
+
Runs when Claude Code is about to ask you for permission to use a tool. In sessions that can’t show a prompt, such as background subagents in [non-interactive mode](https://code.claude.com/docs/en/headless), Claude Code still runs these hooks, and if no hook returns a decision, it denies the tool call. For a call that reaches a `--permission-prompt-tool` or the Agent SDK’s [`canUseTool` callback](https://code.claude.com/docs/en/agent-sdk/permissions), the hooks run alongside your host, and whichever decides first applies.
|
|
1759
|
+
Use [PermissionRequest decision control](https://code.claude.com/docs/en/hooks#permissionrequest-decision-control) to allow or deny on behalf of the user.Use this event when you need a signal the moment Claude asks for permission to use a tool. Claude Code runs a [Notification](https://code.claude.com/docs/en/hooks#notification) hook with the `permission_prompt` type only after the prompt has waited about six seconds.Claude Code doesn’t run PermissionRequest hooks for a sandboxed command’s [network request](https://code.claude.com/docs/en/sandboxing#network-isolation). To get a signal for that prompt, use the `permission_prompt` notification type.Matches on tool name, same values as PreToolUse.
|
|
1416
1760
|
|
|
1417
1761
|
#### [](https://code.claude.com/docs/en/hooks\#permissionrequest-input) PermissionRequest input
|
|
1418
1762
|
|
|
1419
|
-
PermissionRequest hooks receive `tool_name` and `tool_input` fields like PreToolUse hooks, but without `tool_use_id`. An optional `permission_suggestions` array contains the
|
|
1763
|
+
PermissionRequest hooks receive `tool_name` and `tool_input` fields like PreToolUse hooks, but without `tool_use_id`. For an MCP tool, they also receive the [`mcp_server`](https://code.claude.com/docs/en/hooks#pretooluse-input) object. An optional `permission_suggestions` array contains the [permission updates](https://code.claude.com/docs/en/hooks#permission-update-entries) Claude Code suggests for this request, such as adding an allow rule or changing the permission mode.The `permission_suggestions` array isn’t an exact list of the options you see, because each permission dialog builds its own options. Some dialogs, such as the one for file edits, don’t read the array at all and derive their options from the request itself. A dialog that does read it can still withhold an option whose suggestion stays in the array, for example when [`allowManagedPermissionRulesOnly`](https://code.claude.com/docs/en/settings-reference#allowmanagedpermissionrulesonly) hides rule-saving options. It can also offer options that have no suggestion entry, such as [**Yes, and switch to auto mode**](https://code.claude.com/docs/en/permission-modes#switch-permission-modes), which changes the permission mode directly rather than through a permission update.PreToolUse hooks run before every tool call, whether or not it needs permission. PermissionRequest hooks run only when Claude Code is about to ask you for permission, or when it would otherwise auto-deny a call that can’t prompt. Neither event fires for [`EndConversation`](https://code.claude.com/docs/en/tools-reference#endconversation-tool-behavior).
|
|
1420
1764
|
|
|
1421
1765
|
```
|
|
1422
1766
|
{
|
|
@@ -1453,6 +1797,8 @@ PermissionRequest hooks receive `tool_name` and `tool_input` fields like PreTool
|
|
|
1453
1797
|
| `message` | For `"deny"` only: tells Claude why the permission was denied |
|
|
1454
1798
|
| `interrupt` | For `"deny"` only: if `true`, stops Claude |
|
|
1455
1799
|
|
|
1800
|
+
A hook that exits 2 without a `decision` object leaves the permission flow unchanged, and its stderr is discarded. To grant or deny the request, return the `decision` object.
|
|
1801
|
+
|
|
1456
1802
|
```
|
|
1457
1803
|
{
|
|
1458
1804
|
"hookSpecificOutput": {
|
|
@@ -1476,11 +1822,11 @@ The `updatedPermissions` output field and the [`permission_suggestions` input fi
|
|
|
1476
1822
|
| `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"` |
|
|
1477
1823
|
| `replaceRules` | `rules`, `behavior`, `destination` | Replaces all rules of the given `behavior` at the `destination` with the provided `rules` |
|
|
1478
1824
|
| `removeRules` | `rules`, `behavior`, `destination` | Removes matching rules of the given `behavior` |
|
|
1479
|
-
| `setMode` | `mode`, `destination` | Changes the permission mode. Valid modes are `default`, `auto`, `acceptEdits`, `dontAsk`, `bypassPermissions`, `plan`, and `manual` as an alias for `default
|
|
1825
|
+
| `setMode` | `mode`, `destination` | Changes the permission mode. Valid modes are `default`, `auto`, `acceptEdits`, `dontAsk`, `bypassPermissions`, `plan`, and `manual` as an alias for `default` |
|
|
1480
1826
|
| `addDirectories` | `directories`, `destination` | Adds working directories. `directories` is an array of path strings |
|
|
1481
1827
|
| `removeDirectories` | `directories`, `destination` | Removes working directories |
|
|
1482
1828
|
|
|
1483
|
-
`setMode` with `bypassPermissions` only takes effect if the session
|
|
1829
|
+
`setMode` with `bypassPermissions` only takes effect if you launched the session with bypass mode already available: `--dangerously-skip-permissions`, `--permission-mode bypassPermissions`, `--allow-dangerously-skip-permissions`, or `permissions.defaultMode: "bypassPermissions"` in [user, `--settings`, or managed settings](https://code.claude.com/docs/en/settings-reference#permissions-defaultmode). Otherwise the update is a no-op. The update is also a no-op when [`permissions.disableBypassPermissionsMode`](https://code.claude.com/docs/en/permissions#managed-settings) disables the mode, or when the session starts in [restricted mode](https://code.claude.com/docs/en/cli-reference#cli-flags).`bypassPermissions` is never persisted as `defaultMode` regardless of `destination`.
|
|
1484
1830
|
|
|
1485
1831
|
The `destination` field on every entry determines whether the change stays in memory or persists to a settings file.
|
|
1486
1832
|
|
|
@@ -1491,15 +1837,18 @@ The `destination` field on every entry determines whether the change stays in me
|
|
|
1491
1837
|
| `projectSettings` | `.claude/settings.json` |
|
|
1492
1838
|
| `userSettings` | `~/.claude/settings.json` |
|
|
1493
1839
|
|
|
1494
|
-
A hook can echo one of the `permission_suggestions` it received as its own `updatedPermissions` output
|
|
1840
|
+
A hook can echo one of the `permission_suggestions` it received as its own `updatedPermissions` output.
|
|
1495
1841
|
|
|
1496
1842
|
### [](https://code.claude.com/docs/en/hooks\#posttooluse) PostToolUse
|
|
1497
1843
|
|
|
1498
|
-
Runs immediately after a tool completes successfully.Matches on tool name, same values as PreToolUse.
|
|
1844
|
+
Runs immediately after a tool completes successfully.Matches on tool name, same values as PreToolUse.Match more broadly when the tool name isn’t the right filter:
|
|
1845
|
+
|
|
1846
|
+
- To run a hook after any tool completes successfully, omit the `matcher` or set it to `"*"`. Your hook can then discover what changed itself, for example by running `git status --porcelain`, which also lists untracked files that `git diff` misses. For tool calls that fail, add the same hook under [PostToolUseFailure](https://code.claude.com/docs/en/hooks#posttoolusefailure).
|
|
1847
|
+
- To run a hook when a specific file changes on disk, whatever wrote it, use [FileChanged](https://code.claude.com/docs/en/hooks#filechanged). Claude Code doesn’t run a `PostToolUse` hook matching `Edit|Write` when a `Bash` command or a process outside Claude Code rewrites the same file.
|
|
1499
1848
|
|
|
1500
1849
|
#### [](https://code.claude.com/docs/en/hooks\#posttooluse-input) PostToolUse input
|
|
1501
1850
|
|
|
1502
|
-
`PostToolUse` hooks fire after a tool has already executed successfully. The input includes both `tool_input`, the arguments sent to the tool, and `tool_response`, the result it returned. The exact schema for both depends on the tool.
|
|
1851
|
+
`PostToolUse` hooks fire after a tool has already executed successfully. The input includes both `tool_input`, the arguments sent to the tool, and `tool_response`, the result it returned. The exact schema for both depends on the tool. File-tool `tool_input` paths arrive in the same format as for [PreToolUse](https://code.claude.com/docs/en/hooks#pretooluse-input): always absolute, with the platform’s native separators, so backslashes on Windows. For an MCP tool, the input also carries the [`mcp_server`](https://code.claude.com/docs/en/hooks#pretooluse-input) object.
|
|
1503
1852
|
|
|
1504
1853
|
```
|
|
1505
1854
|
{
|
|
@@ -1515,7 +1864,7 @@ Runs immediately after a tool completes successfully.Matches on tool name, same
|
|
|
1515
1864
|
},
|
|
1516
1865
|
"tool_response": {
|
|
1517
1866
|
"filePath": "/path/to/file.txt",
|
|
1518
|
-
"
|
|
1867
|
+
"type": "create"
|
|
1519
1868
|
},
|
|
1520
1869
|
"tool_use_id": "toolu_01ABC123...",
|
|
1521
1870
|
"duration_ms": 12
|
|
@@ -1535,6 +1884,7 @@ Runs immediately after a tool completes successfully.Matches on tool name, same
|
|
|
1535
1884
|
| `decision` | `"block"` adds the `reason` next to the tool result. Claude still sees the original output; to replace it, use `updatedToolOutput` |
|
|
1536
1885
|
| `reason` | Explanation shown to Claude when `decision` is `"block"` |
|
|
1537
1886
|
| `additionalContext` | String added to Claude’s context alongside the tool result. See [Add context for Claude](https://code.claude.com/docs/en/hooks#add-context-for-claude) |
|
|
1887
|
+
| `classifierContext` | Short note about this call’s result for the [auto mode](https://code.claude.com/docs/en/permission-modes#eliminate-prompts-with-auto-mode) classifier rather than for Claude. See [Annotate a result for the auto mode classifier](https://code.claude.com/docs/en/hooks#annotate-a-result-for-the-auto-mode-classifier). Requires Claude Code v2.1.236 or later |
|
|
1538
1888
|
| `updatedToolOutput` | Replaces the tool’s output with the provided value before it is sent to Claude. The value must match the tool’s output shape |
|
|
1539
1889
|
| `updatedMCPToolOutput` | Replaces the output for [MCP tools](https://code.claude.com/docs/en/hooks#match-mcp-tools) only. Prefer `updatedToolOutput`, which works for all tools |
|
|
1540
1890
|
|
|
@@ -1557,13 +1907,42 @@ The example below replaces the output of a `Bash` call. The replacement value ma
|
|
|
1557
1907
|
|
|
1558
1908
|
`updatedToolOutput` only changes what Claude sees. The tool has already run by the time the hook fires, so any files written, commands executed, or network requests sent have already taken effect. Telemetry such as OpenTelemetry tool spans and analytics events also captures the original output before the hook runs. To prevent or modify a tool call before it runs, use a [PreToolUse](https://code.claude.com/docs/en/hooks#pretooluse) hook instead.The replacement value must match the tool’s output shape. Built-in tools return structured objects rather than plain strings. For example, `Bash` returns an object with `stdout`, `stderr`, `interrupted`, and `isImage` fields. For built-in tools, a value that doesn’t match the tool’s output schema is ignored and the original output is used. MCP tool output is passed through without schema validation. Stripping error details that Claude needs can cause it to proceed on a false assumption.
|
|
1559
1909
|
|
|
1910
|
+
#### [](https://code.claude.com/docs/en/hooks\#annotate-a-result-for-the-auto-mode-classifier) Annotate a result for the auto mode classifier
|
|
1911
|
+
|
|
1912
|
+
Return `classifierContext` to send a short note about the tool call’s result to the [auto mode](https://code.claude.com/docs/en/permission-modes#eliminate-prompts-with-auto-mode) classifier rather than to Claude. The classifier [never receives tool results themselves](https://code.claude.com/docs/en/permission-modes#how-the-classifier-evaluates-actions), so this field is the supported way to tell it something about what a call returned before it reviews later actions. The field requires Claude Code v2.1.236 or later.The example below tells the classifier where a query’s output came from:
|
|
1913
|
+
|
|
1914
|
+
```
|
|
1915
|
+
{
|
|
1916
|
+
"hookSpecificOutput": {
|
|
1917
|
+
"hookEventName": "PostToolUse",
|
|
1918
|
+
"classifierContext": "This query ran against the staging database, not production."
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
```
|
|
1922
|
+
|
|
1923
|
+
How much weight the classifier gives the note depends on where you configured the hook:
|
|
1924
|
+
|
|
1925
|
+
- **Hooks configured in Claude Code**: for hooks from settings files, plugins, skills, and agent frontmatter, the classifier treats the note as unverified, application-provided context. The note never establishes user intent, and if it claims you approved or requested something, the classifier checks that claim against your own messages in the conversation
|
|
1926
|
+
- **In-process Agent SDK callbacks**: when an application embedding Claude Code registers the hook as a [TypeScript SDK callback](https://code.claude.com/docs/en/agent-sdk/hooks) and returns the note during the live session, the classifier may weigh a user statement relayed in the note as user intent. Such a statement can satisfy a consent requirement the classifier would accept from a message you send, but it never lifts a block that your own message couldn’t lift either. After a session resumes, Claude Code treats restored notes as unverified context. When hooks from both groups annotate the same call, the classifier treats the combined note as unverified
|
|
1927
|
+
|
|
1928
|
+
Claude Code applies these limits when delivering the note:
|
|
1929
|
+
|
|
1930
|
+
- **Length**: Claude Code caps the notes for one tool call at 2,000 characters and truncates the rest. The cap is shared across every hook that responds to that call
|
|
1931
|
+
- **Synchronous responses only**: Claude Code ignores the field in the response of a hook that [runs in the background](https://code.claude.com/docs/en/hooks#run-hooks-in-the-background), because that response arrives after Claude Code records the tool result
|
|
1932
|
+
- **Calls that the classifier doesn’t record**: the classifier’s transcript omits read-only lookups such as file reads and searches. Claude Code discards a note attached to one of those calls
|
|
1933
|
+
- **Interaction with rewrites**: when the note describes output you’re replacing with `updatedToolOutput`, return both fields in the same hook response. Claude Code drops the note if that rewrite is rejected or another hook’s rewrite replaces it. Claude Code delivers a note you return without a rewrite even when another hook rewrites the output
|
|
1934
|
+
|
|
1935
|
+
The classifier reads content you place in `classifierContext` as information from the application hosting the session, so don’t copy untrusted tool output or third-party text into it. Keep the note to a short assertion about this one call, such as a fact about its origin or a user statement about it; don’t use the field to deliver unrelated messages or a stream of events.
|
|
1936
|
+
|
|
1560
1937
|
### [](https://code.claude.com/docs/en/hooks\#posttoolusefailure) PostToolUseFailure
|
|
1561
1938
|
|
|
1562
|
-
Runs when a tool
|
|
1939
|
+
Runs when a tool that started executing fails: the tool threw an error, or an MCP tool returned an error result. Use this to log failures, send alerts, or provide corrective feedback to Claude.Matches on tool name, same values as PreToolUse.
|
|
1940
|
+
|
|
1941
|
+
This event doesn’t fire for tool calls rejected before execution: an unknown tool name, input that fails schema or tool-specific validation, or a permission denial. Validation rejections are returned as `tool_use_error` results and happen before hooks run, so they fire neither `PreToolUse` nor `PostToolUseFailure`. Permission denials fire `PreToolUse` but not this event; see [PermissionDenied](https://code.claude.com/docs/en/hooks#permissiondenied).
|
|
1563
1942
|
|
|
1564
1943
|
#### [](https://code.claude.com/docs/en/hooks\#posttoolusefailure-input) PostToolUseFailure input
|
|
1565
1944
|
|
|
1566
|
-
PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as PostToolUse, along with error information as top-level fields:
|
|
1945
|
+
PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as PostToolUse, along with error information as top-level fields. For an MCP tool, they also receive the [`mcp_server`](https://code.claude.com/docs/en/hooks#pretooluse-input) object. For example, a failed `npm test` command might deliver:
|
|
1567
1946
|
|
|
1568
1947
|
```
|
|
1569
1948
|
{
|
|
@@ -1578,7 +1957,7 @@ PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as
|
|
|
1578
1957
|
"description": "Run test suite"
|
|
1579
1958
|
},
|
|
1580
1959
|
"tool_use_id": "toolu_01ABC123...",
|
|
1581
|
-
"error": "
|
|
1960
|
+
"error": "Exit code 1\nError: Cannot find module 'express'",
|
|
1582
1961
|
"is_interrupt": false,
|
|
1583
1962
|
"duration_ms": 4187
|
|
1584
1963
|
}
|
|
@@ -1586,10 +1965,16 @@ PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as
|
|
|
1586
1965
|
|
|
1587
1966
|
| Field | Description |
|
|
1588
1967
|
| --- | --- |
|
|
1589
|
-
| `error` | String describing what went wrong |
|
|
1590
|
-
| `is_interrupt` | Optional boolean
|
|
1968
|
+
| `error` | String describing what went wrong. The format depends on the tool that failed |
|
|
1969
|
+
| `is_interrupt` | Optional boolean. True when the failure reached Claude Code as an abort rather than as an error the tool reported. Cancelling a running tool does not fire this hook; the tool result carries the interruption message instead |
|
|
1591
1970
|
| `duration_ms` | Optional. Tool execution time in milliseconds. Excludes time spent in permission prompts and PreToolUse hooks |
|
|
1592
1971
|
|
|
1972
|
+
The `error` string is generally the same text Claude receives as the failed tool’s result. Its format varies by tool and failure. Key your hook on `tool_name`, `is_interrupt`, and the `Exit code N` first line; treat the rest of the string as display text, not a stable format.
|
|
1973
|
+
|
|
1974
|
+
- For Bash and PowerShell, a command that ran and exited produces a first line `Exit code N`, then any output the command produced as one block with stdout and stderr interleaved
|
|
1975
|
+
- A payload may also carry a bare failure message with no exit-code line, when Claude Code could not start the shell process itself
|
|
1976
|
+
- Claude Code middle-truncates long strings around a `... [N characters truncated] ...` marker, and can insert lines of its own, such as `Command timed out after 2m 0s`
|
|
1977
|
+
|
|
1593
1978
|
#### [](https://code.claude.com/docs/en/hooks\#posttoolusefailure-decision-control) PostToolUseFailure decision control
|
|
1594
1979
|
|
|
1595
1980
|
`PostToolUseFailure` hooks can provide context to Claude after a tool failure. In addition to the [JSON output fields](https://code.claude.com/docs/en/hooks#json-output) available to all hooks, your hook script can return these event-specific fields:
|
|
@@ -1627,13 +2012,13 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
1627
2012
|
"tool_name": "Read",\
|
|
1628
2013
|
"tool_input": {"file_path": "/.../ledger/accounts.py"},\
|
|
1629
2014
|
"tool_use_id": "toolu_01...",\
|
|
1630
|
-
"tool_response": "
|
|
2015
|
+
"tool_response": "1\tfrom __future__ import annotations\n2\t..."\
|
|
1631
2016
|
},\
|
|
1632
2017
|
{\
|
|
1633
2018
|
"tool_name": "Read",\
|
|
1634
2019
|
"tool_input": {"file_path": "/.../ledger/transactions.py"},\
|
|
1635
2020
|
"tool_use_id": "toolu_02...",\
|
|
1636
|
-
"tool_response": "
|
|
2021
|
+
"tool_response": "1\tfrom __future__ import annotations\n2\t..."\
|
|
1637
2022
|
}\
|
|
1638
2023
|
]
|
|
1639
2024
|
}
|
|
@@ -1641,7 +2026,7 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
1641
2026
|
|
|
1642
2027
|
`tool_response` contains the same content the model receives in the corresponding `tool_result` block. The value is a serialized string or content-block array, exactly as the tool emitted it. For `Read`, that means line-number-prefixed text rather than raw file contents. Responses can be large, so parse only the fields you need.
|
|
1643
2028
|
|
|
1644
|
-
The `tool_response` shape differs from `PostToolUse`’s. `PostToolUse` passes the tool’s structured `Output` object, such as `{filePath: "...",
|
|
2029
|
+
The `tool_response` shape differs from `PostToolUse`’s. `PostToolUse` passes the tool’s structured `Output` object, such as `{filePath: "...", type: "create"}` for `Write`; `PostToolBatch` passes the serialized `tool_result` content the model sees.
|
|
1645
2030
|
|
|
1646
2031
|
#### [](https://code.claude.com/docs/en/hooks\#posttoolbatch-decision-control) PostToolBatch decision control
|
|
1647
2032
|
|
|
@@ -1660,15 +2045,15 @@ The `tool_response` shape differs from `PostToolUse`’s. `PostToolUse` passes t
|
|
|
1660
2045
|
}
|
|
1661
2046
|
```
|
|
1662
2047
|
|
|
1663
|
-
Returning `decision: "block"` or `continue: false` stops the agentic loop before the next model call.
|
|
2048
|
+
Returning `decision: "block"` or `continue: false` stops the agentic loop before the next model call. The blocking message comes from the JSON `reason` or `stopReason`, or from stderr on exit 2. You see it as a warning in the transcript, and it stays in the conversation, so Claude sees it when the conversation continues.
|
|
1664
2049
|
|
|
1665
2050
|
### [](https://code.claude.com/docs/en/hooks\#permissiondenied) PermissionDenied
|
|
1666
2051
|
|
|
1667
|
-
Runs when
|
|
2052
|
+
Runs when [auto mode](https://code.claude.com/docs/en/permission-modes#eliminate-prompts-with-auto-mode) denies a tool call, including when it denies without a classifier verdict because [a safety check separate from auto mode refused the classifier’s own request](https://code.claude.com/docs/en/errors#auto-mode-cannot-determine-the-safety-of-an-action) or its response didn’t parse. This hook only fires in auto mode: it doesn’t run when you manually deny a permission dialog, when a `PreToolUse` hook blocks a call, or when a `deny` rule matches. Use it to log denials, adjust configuration, or tell the model it may retry the tool call.Matches on tool name, same values as PreToolUse.
|
|
1668
2053
|
|
|
1669
2054
|
#### [](https://code.claude.com/docs/en/hooks\#permissiondenied-input) PermissionDenied input
|
|
1670
2055
|
|
|
1671
|
-
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields), PermissionDenied hooks receive `tool_name`, `tool_input`, `tool_use_id`, and `reason`.
|
|
2056
|
+
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields), PermissionDenied hooks receive `tool_name`, `tool_input`, `tool_use_id`, and `reason`. For an MCP tool, they also receive the [`mcp_server`](https://code.claude.com/docs/en/hooks#pretooluse-input) object.
|
|
1672
2057
|
|
|
1673
2058
|
```
|
|
1674
2059
|
{
|
|
@@ -1683,13 +2068,13 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
1683
2068
|
"description": "Clean build directory"
|
|
1684
2069
|
},
|
|
1685
2070
|
"tool_use_id": "toolu_01ABC123...",
|
|
1686
|
-
"reason": "
|
|
2071
|
+
"reason": "[Irreversible Local Destruction]"
|
|
1687
2072
|
}
|
|
1688
2073
|
```
|
|
1689
2074
|
|
|
1690
2075
|
| Field | Description |
|
|
1691
2076
|
| --- | --- |
|
|
1692
|
-
| `reason` | The classifier
|
|
2077
|
+
| `reason` | The denial reason. For a classifier verdict, in most sessions it names the matched rule in square brackets, such as `[Data Exfiltration]`; see [Review denials](https://code.claude.com/docs/en/auto-mode-config#review-denials) for the other forms. For a [no-verdict denial](https://code.claude.com/docs/en/hooks#permissiondenied-decision-control), it starts with `Auto mode could not evaluate this action and is blocking it for safety`. For a denial because the classifier model was unavailable, it is the fixed text `Classifier unavailable` |
|
|
1693
2078
|
|
|
1694
2079
|
#### [](https://code.claude.com/docs/en/hooks\#permissiondenied-decision-control) PermissionDenied decision control
|
|
1695
2080
|
|
|
@@ -1704,24 +2089,44 @@ PermissionDenied hooks can tell the model it may retry the denied tool call. Ret
|
|
|
1704
2089
|
}
|
|
1705
2090
|
```
|
|
1706
2091
|
|
|
1707
|
-
When `retry` is `true`, Claude Code adds a message to the conversation telling the model it may retry the tool call.
|
|
2092
|
+
When `retry` is `true`, Claude Code adds a message to the conversation telling the model it may retry the tool call. Claude Code doesn’t reverse the denial itself. If your hook doesn’t return JSON, or returns `retry: false`, the denial stands and the model receives the original rejection message.Claude Code ignores `retry: true` when the classifier produced [no verdict on the action](https://code.claude.com/docs/en/errors#auto-mode-cannot-determine-the-safety-of-an-action): its response didn’t parse, or a safety check separate from auto mode refused the classifier’s own request. For those denials, Claude Code already tells the model in the rejection message whether to retry later or move on.
|
|
1708
2093
|
|
|
1709
2094
|
### [](https://code.claude.com/docs/en/hooks\#notification) Notification
|
|
1710
2095
|
|
|
1711
|
-
Runs when Claude Code sends notifications. Matches on notification type. Omit the matcher to run hooks for all notification types.
|
|
2096
|
+
Runs when Claude Code sends notifications. Matches on notification type. Omit the matcher to run hooks for all notification types.You receive these hook events even with desktop notifications turned off: the `preferredNotifChannel` setting, including `notifications_disabled`, changes only how you’re alerted, not whether your hook runs.
|
|
1712
2097
|
|
|
1713
2098
|
| Matcher | When it fires |
|
|
1714
2099
|
| --- | --- |
|
|
1715
|
-
| `permission_prompt` | Claude needs you to approve a tool use |
|
|
1716
|
-
| `idle_prompt` | Claude
|
|
2100
|
+
| `permission_prompt` | Claude needs you to approve a tool use or a sandboxed command’s [network request](https://code.claude.com/docs/en/sandboxing#network-isolation), and the prompt has waited about six seconds |
|
|
2101
|
+
| `idle_prompt` | Claude finished responding about 60 seconds ago and you haven’t typed since |
|
|
1717
2102
|
| `auth_success` | Authentication completes |
|
|
1718
|
-
| `elicitation_dialog` | An MCP server opens an elicitation form |
|
|
1719
|
-
| `
|
|
2103
|
+
| `elicitation_dialog` | An MCP server opens an elicitation form and you haven’t typed for about six seconds |
|
|
2104
|
+
| `elicitation_url_dialog` | An MCP server asks you to open a browser URL and you haven’t typed for about six seconds |
|
|
2105
|
+
| `elicitation_complete` | An MCP server reports that a [URL-mode elicitation](https://code.claude.com/docs/en/hooks#elicitation-input) is complete |
|
|
1720
2106
|
| `elicitation_response` | An MCP elicitation response is sent back to the server |
|
|
1721
|
-
| `agent_needs_input` | A background session starts waiting on your input
|
|
2107
|
+
| `agent_needs_input` | A background session starts waiting on your input while [agent view](https://code.claude.com/docs/en/agent-view) is open in a terminal. Also fires when a terminal session shows you an [agent team teammate’s terminal setup question](https://code.claude.com/docs/en/agent-teams#choose-a-display-mode) or auto mode’s notice about [classifier request charges](https://code.claude.com/docs/en/auto-mode-classifier-billing) and you haven’t typed for about six seconds |
|
|
1722
2108
|
| `agent_completed` | A background session finishes or fails. Fires only while [agent view](https://code.claude.com/docs/en/agent-view) is open in a terminal |
|
|
2109
|
+
| `quota_auto_resume_fired` | Claude Code continues your task after a claude.ai usage limit paused it: at the reset, or sooner when something you do in Claude Code during the wait, such as adding usage credits, upgrading your plan, or switching models, makes usage available again, with the [model-setting exception](https://code.claude.com/docs/en/interactive-mode#wait-for-a-usage-limit-to-reset) |
|
|
2110
|
+
| `quota_auto_resume_stale` | A claude.ai usage limit reset while your computer slept for more than about 30 minutes. Claude Code waits for you to press `Enter` instead of continuing. After a shorter sleep it continues and fires `quota_auto_resume_fired` instead |
|
|
2111
|
+
| `quota_auto_resume_disabled` | Claude Code ends its wait for a claude.ai usage limit without continuing your task: [`autoContinueAtUsageLimit`](https://code.claude.com/docs/en/settings-reference#autocontinueatusagelimit) turned off or the reset moved more than 24 hours away during a wait Claude Code started on its own, the continued task kept hitting the limit, or the continuation was blocked before it reached the model. Doesn’t fire when you press `Esc` or `Ctrl+C`, or pick **Don’t continue automatically** |
|
|
1723
2112
|
|
|
1724
|
-
The `
|
|
2113
|
+
The `quota_auto_resume_fired`, `quota_auto_resume_stale`, and `quota_auto_resume_disabled` types require Claude Code v2.1.234 or later.In terminal sessions, `permission_prompt` for a sandboxed command’s network request requires Claude Code v2.1.246 or later.`agent_needs_input` for a teammate’s terminal setup question requires Claude Code v2.1.248 or later.
|
|
2114
|
+
|
|
2115
|
+
The `permission_prompt`, `idle_prompt`, `elicitation_dialog`, and `elicitation_url_dialog` types share their timing with desktop notifications, so in terminal sessions you only see them when you appear to be away from the terminal:
|
|
2116
|
+
|
|
2117
|
+
- Expect `permission_prompt` once you haven’t typed for about six seconds. The timer starts when the permission prompt appears, and each keystroke defers it. To run a hook immediately when Claude asks for permission to use a tool, use [PermissionRequest](https://code.claude.com/docs/en/hooks#permissionrequest) instead.
|
|
2118
|
+
- Expect `idle_prompt` about 60 seconds after Claude finishes responding, and only if you haven’t typed since and no background agent, such as a background [subagent](https://code.claude.com/docs/en/sub-agents), is still running. Claude Code doesn’t send `idle_prompt` while it waits for a claude.ai usage limit to reset. When the wait ends on its own, one of the `quota_auto_resume_*` types fires instead.
|
|
2119
|
+
- Expect `elicitation_dialog` for an elicitation form, or `elicitation_url_dialog` for a browser URL request, once you haven’t typed for about six seconds. Both share the same six-second gate as `permission_prompt`: the timer starts when the dialog appears, and each keystroke defers it.
|
|
2120
|
+
|
|
2121
|
+
A permission request or elicitation that arrives while another dialog is on screen keeps the same six-second gate, timed from when the request arrives. Its notification can reach you while the request still waits behind the open dialog.
|
|
2122
|
+
|
|
2123
|
+
Claude Code times `permission_prompt` differently in sessions where it sends permission requests to the Agent SDK’s [`canUseTool` callback](https://code.claude.com/docs/en/agent-sdk/user-input), which is how Claude Desktop and the VS Code extension host Claude Code:
|
|
2124
|
+
|
|
2125
|
+
- Expect `permission_prompt` about six seconds after Claude asks for permission. Claude Code doesn’t defer it while you type.
|
|
2126
|
+
- If you or a [PermissionRequest](https://code.claude.com/docs/en/hooks#permissionrequest) hook answer sooner, Claude Code doesn’t run `permission_prompt`.
|
|
2127
|
+
- Set [`CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS`](https://code.claude.com/docs/en/env-vars) to `1` to turn `permission_prompt` off in these sessions.
|
|
2128
|
+
|
|
2129
|
+
Before v2.1.233, `permission_prompt` didn’t fire in these sessions.Use separate matchers to run different handlers depending on the notification type. This configuration triggers a permission-specific alert script when Claude needs permission approval and a different notification when Claude has been idle:
|
|
1725
2130
|
|
|
1726
2131
|
```
|
|
1727
2132
|
{
|
|
@@ -1766,11 +2171,11 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
1766
2171
|
}
|
|
1767
2172
|
```
|
|
1768
2173
|
|
|
1769
|
-
Notification hooks can’t block or modify notifications.
|
|
2174
|
+
Notification hooks can’t block or modify notifications. Claude Code discards their `systemMessage` and `continue` fields but still emits [`terminalSequence`](https://code.claude.com/docs/en/hooks#emit-terminal-notifications), which is what the desktop notification example relies on. Notification hooks are intended for side effects such as forwarding the notification to an external service.
|
|
1770
2175
|
|
|
1771
2176
|
### [](https://code.claude.com/docs/en/hooks\#subagentstart) SubagentStart
|
|
1772
2177
|
|
|
1773
|
-
Runs when a Claude
|
|
2178
|
+
Runs when Claude spawns a subagent with the Agent tool, when Claude [resumes a subagent](https://code.claude.com/docs/en/sub-agents#resume-subagents), and each time an in-process [agent team](https://code.claude.com/docs/en/agent-teams) teammate handles a new message. Supports matchers to filter by agent type name. For built-in agents, this is the agent name like `general-purpose`, `Explore`, or `Plan`. For [custom subagents](https://code.claude.com/docs/en/sub-agents), this is the `name` field from the agent’s frontmatter, not the filename.For subagents shipped by a [plugin](https://code.claude.com/docs/en/plugins/overview), the agent type is the plugin-scoped identifier such as `my-plugin:reviewer`, not the bare frontmatter name. The colon places a plugin-scoped name on the regular-expression path, so anchor the matcher with `^` and `$` for an exact match: `^my-plugin:reviewer$`.
|
|
1774
2179
|
|
|
1775
2180
|
#### [](https://code.claude.com/docs/en/hooks\#subagentstart-input) SubagentStart input
|
|
1776
2181
|
|
|
@@ -1802,13 +2207,15 @@ SubagentStart hooks can’t block subagent creation, but they can inject context
|
|
|
1802
2207
|
}
|
|
1803
2208
|
```
|
|
1804
2209
|
|
|
2210
|
+
When the hook runs again for the same subagent, Claude Code injects the returned context only when the subagent’s context doesn’t already hold the copy from an earlier run. The copy injected at launch stays in place, leaving the subagent’s [prompt cache](https://code.claude.com/docs/en/prompt-caching#subagents-and-the-cache) intact. After [auto-compaction](https://code.claude.com/docs/en/sub-agents#auto-compaction) discards that copy, Claude Code injects the next run’s context again.
|
|
2211
|
+
|
|
1805
2212
|
### [](https://code.claude.com/docs/en/hooks\#subagentstop) SubagentStop
|
|
1806
2213
|
|
|
1807
2214
|
Runs when a Claude Code subagent has finished responding. Matches on agent type, same values as SubagentStart.
|
|
1808
2215
|
|
|
1809
2216
|
#### [](https://code.claude.com/docs/en/hooks\#subagentstop-input) SubagentStop input
|
|
1810
2217
|
|
|
1811
|
-
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#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.SubagentStop
|
|
2218
|
+
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#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.Not every SubagentStop event comes from a subagent Claude spawned. Claude Code also runs internal agents for some of its own features, such as [prompt suggestions](https://code.claude.com/docs/en/interactive-mode#prompt-suggestions) and [`/btw` side questions](https://code.claude.com/docs/en/interactive-mode#side-questions-with-%2Fbtw), and SubagentStop fires when one of those finishes too. For those events, `agent_type` is the agent name the session itself runs as, such as one set with [`--agent`](https://code.claude.com/docs/en/cli-reference#cli-flags) or the [`agent` setting](https://code.claude.com/docs/en/settings-reference#agent), and an empty string when the session runs without one.A `matcher` that names agent types doesn’t match an empty `agent_type`. A hook whose matcher is omitted, `""`, or `"*"`, or is a regular expression that matches an empty string, runs for events with an empty `agent_type` too.On Claude Code v2.1.271 or later, a subagent that runs with the [`SubagentHandback`](https://code.claude.com/docs/en/tools-reference) tool delivers its report through that tool before it stops. The `last_assistant_message` field then holds the subagent’s closing text, if any, which is not the delivered report. The report is that call’s `message` input, which a `PreToolUse` or `PostToolUse` hook matched on `SubagentHandback` receives as `tool_input.message`.SubagentStop hooks also receive the `background_tasks` and `session_crons` arrays described under [Stop input](https://code.claude.com/docs/en/hooks#stop-input). Both arrays are scoped to the parent session, not the subagent.
|
|
1812
2219
|
|
|
1813
2220
|
```
|
|
1814
2221
|
{
|
|
@@ -1827,11 +2234,11 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
1827
2234
|
}
|
|
1828
2235
|
```
|
|
1829
2236
|
|
|
1830
|
-
SubagentStop hooks use the same decision control format as [Stop hooks](https://code.claude.com/docs/en/hooks#stop-decision-control), including `hookSpecificOutput.additionalContext` with `hookEventName` set to `"SubagentStop"`, for non-error feedback that keeps the subagent running. Returning `decision: "block"` with a `reason` keeps the subagent running and delivers `reason` to the subagent as its next instruction. To inject context into the parent session after a subagent returns, use a [`PostToolUse`](https://code.claude.com/docs/en/hooks#posttooluse) hook on the `Agent` tool instead.
|
|
2237
|
+
SubagentStop hooks use the same decision control format as [Stop hooks](https://code.claude.com/docs/en/hooks#stop-decision-control), including `hookSpecificOutput.additionalContext` with `hookEventName` set to `"SubagentStop"`, for non-error feedback that keeps the subagent running. Returning `decision: "block"` with a `reason` keeps the subagent running and delivers `reason` to the subagent as its next instruction. A hook that blocks by exiting 2 delivers its stderr message the same way. To inject context into the parent session after a subagent returns, use a [`PostToolUse`](https://code.claude.com/docs/en/hooks#posttooluse) hook on the `Agent` tool instead.
|
|
1831
2238
|
|
|
1832
2239
|
### [](https://code.claude.com/docs/en/hooks\#taskcreated) TaskCreated
|
|
1833
2240
|
|
|
1834
|
-
Runs 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.
|
|
2241
|
+
Runs 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. In a [session without the Task tools](https://code.claude.com/docs/en/tools-reference#task-tool-availability), this event doesn’t fire.TaskCreated hooks don’t support matchers and fire on every occurrence.
|
|
1835
2242
|
|
|
1836
2243
|
#### [](https://code.claude.com/docs/en/hooks\#taskcreated-input) TaskCreated input
|
|
1837
2244
|
|
|
@@ -1842,7 +2249,6 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
1842
2249
|
"session_id": "abc123",
|
|
1843
2250
|
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
|
|
1844
2251
|
"cwd": "/Users/...",
|
|
1845
|
-
"permission_mode": "default",
|
|
1846
2252
|
"hook_event_name": "TaskCreated",
|
|
1847
2253
|
"task_id": "task-001",
|
|
1848
2254
|
"task_subject": "Implement user authentication",
|
|
@@ -1859,13 +2265,14 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
1859
2265
|
| `task_description` | Detailed description of the task. May be absent |
|
|
1860
2266
|
| `teammate_name` | Name of the teammate creating the task. May be absent |
|
|
1861
2267
|
| `team_name` | Deprecated. Session-derived team name; will be removed in a future release |
|
|
2268
|
+
| `agent_id` | On this event, the [common input field](https://code.claude.com/docs/en/hooks#common-input-fields) identifies the subagent or [in-process teammate](https://code.claude.com/docs/en/agent-teams#choose-a-display-mode) creating the task. May be absent. Requires Claude Code v2.1.290 or later |
|
|
1862
2269
|
|
|
1863
2270
|
#### [](https://code.claude.com/docs/en/hooks\#taskcreated-decision-control) TaskCreated decision control
|
|
1864
2271
|
|
|
1865
|
-
TaskCreated
|
|
2272
|
+
A TaskCreated hook can block the creation with exit code 2 or with a JSON decision. Either way, Claude Code deletes the task and returns your message to Claude as the tool’s error. Claude Code ignores `continue: false` from this event and Claude keeps working.
|
|
1866
2273
|
|
|
1867
|
-
- **Exit code 2**:
|
|
1868
|
-
- **JSON `{"
|
|
2274
|
+
- **Exit code 2**: Claude Code returns the stderr text as the message.
|
|
2275
|
+
- **JSON `{"decision": "block", "reason": "..."}`**: Claude Code returns `reason` as the message.
|
|
1869
2276
|
|
|
1870
2277
|
This example blocks tasks whose subjects don’t follow the required format:
|
|
1871
2278
|
|
|
@@ -1884,7 +2291,7 @@ exit 0
|
|
|
1884
2291
|
|
|
1885
2292
|
### [](https://code.claude.com/docs/en/hooks\#taskcompleted) TaskCompleted
|
|
1886
2293
|
|
|
1887
|
-
Runs 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](https://code.claude.com/docs/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.
|
|
2294
|
+
Runs 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](https://code.claude.com/docs/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.TaskCompleted hooks don’t support matchers and fire on every occurrence.
|
|
1888
2295
|
|
|
1889
2296
|
#### [](https://code.claude.com/docs/en/hooks\#taskcompleted-input) TaskCompleted input
|
|
1890
2297
|
|
|
@@ -1912,13 +2319,14 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
1912
2319
|
| `task_description` | Detailed description of the task. May be absent |
|
|
1913
2320
|
| `teammate_name` | Name of the teammate completing the task. May be absent |
|
|
1914
2321
|
| `team_name` | Deprecated. Session-derived team name; will be removed in a future release |
|
|
2322
|
+
| `agent_id` | On this event, the [common input field](https://code.claude.com/docs/en/hooks#common-input-fields) identifies the subagent or [in-process teammate](https://code.claude.com/docs/en/agent-teams#choose-a-display-mode) completing the task. May be absent. Requires Claude Code v2.1.290 or later |
|
|
1915
2323
|
|
|
1916
2324
|
#### [](https://code.claude.com/docs/en/hooks\#taskcompleted-decision-control) TaskCompleted decision control
|
|
1917
2325
|
|
|
1918
2326
|
TaskCompleted hooks support two ways to control task completion:
|
|
1919
2327
|
|
|
1920
2328
|
- **Exit code 2**: the task is not marked as completed and the stderr message is fed back to the model as feedback.
|
|
1921
|
-
- **JSON `{"continue": false, "stopReason": "..."}`**: stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user.
|
|
2329
|
+
- **JSON `{"continue": false, "stopReason": "..."}`**: when a teammate finishing its turn triggered the event, stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user. When the `TaskUpdate` tool triggered the event, Claude Code ignores `continue: false`; exit code 2 still blocks the completion.
|
|
1922
2330
|
|
|
1923
2331
|
This example runs tests and blocks task completion if they fail:
|
|
1924
2332
|
|
|
@@ -1942,11 +2350,11 @@ Runs when the main Claude Code agent has finished responding. Does not run if
|
|
|
1942
2350
|
the stoppage occurred due to a user interrupt. API errors fire
|
|
1943
2351
|
[StopFailure](https://code.claude.com/docs/en/hooks#stopfailure) instead.
|
|
1944
2352
|
|
|
1945
|
-
The [`/goal`](https://code.claude.com/docs/en/goal) command is a built-in shortcut for a session-scoped prompt-based Stop hook. Use it when you want Claude to keep working
|
|
2353
|
+
The [`/goal`](https://code.claude.com/docs/en/goal) command is a built-in shortcut for a session-scoped prompt-based Stop hook. Use it when you want Claude to keep working toward a condition without writing hook configuration.
|
|
1946
2354
|
|
|
1947
2355
|
#### [](https://code.claude.com/docs/en/hooks\#stop-input) Stop input
|
|
1948
2356
|
|
|
1949
|
-
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields), Stop hooks receive `stop_hook_active`, `last_assistant_message`, `background_tasks`, and `session_crons`. 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 avoid blocking on a condition that will never resolve. Claude Code overrides the
|
|
2357
|
+
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields), Stop hooks receive `stop_hook_active`, `last_assistant_message`, `background_tasks`, and `session_crons`. 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 avoid blocking on a condition that will never resolve.Claude Code applies an 8-consecutive-continuation cap: after stop hooks have continued the turn eight times in a row, Claude Code overrides the next block and ends the turn. The count of consecutive continuations resets each time Claude calls a tool. To raise the cap, set [`CLAUDE_CODE_STOP_HOOK_BLOCK_CAP`](https://code.claude.com/docs/en/env-vars).The `last_assistant_message` field contains the text content of Claude’s final response, so hooks can access it without parsing the transcript file. For hooks that act on the just-completed turn, such as read-aloud or notification hooks, use this field rather than reading `transcript_path`: the transcript file isn’t guaranteed to include the final message at Stop time on all versions.The `background_tasks` and `session_crons` arrays let hooks distinguish “session is done” from “session is paused waiting for background work to wake it back up”. Both arrays are present when the task registry is reachable and are empty when nothing is in flight or scheduled.Each entry in `background_tasks` describes one in-flight task and uses these fields:
|
|
1950
2358
|
|
|
1951
2359
|
| Field | Description |
|
|
1952
2360
|
| --- | --- |
|
|
@@ -2010,6 +2418,8 @@ This example shows a Stop input with one in-flight shell task and one recurring
|
|
|
2010
2418
|
| `reason` | Required when `decision` is `"block"`. Tells Claude why it should continue |
|
|
2011
2419
|
| `hookSpecificOutput.additionalContext` | Non-error feedback for Claude. The conversation continues so Claude can act on it, but unlike `decision: "block"` it is shown in the transcript as hook feedback rather than a hook error |
|
|
2012
2420
|
|
|
2421
|
+
A hook that blocks by exiting 2 routes the same way as `reason`: Claude receives the stderr message as the explanation for why it should continue.
|
|
2422
|
+
|
|
2013
2423
|
```
|
|
2014
2424
|
{
|
|
2015
2425
|
"decision": "block",
|
|
@@ -2030,7 +2440,7 @@ Use `additionalContext` when the hook is working as designed and giving Claude g
|
|
|
2030
2440
|
|
|
2031
2441
|
### [](https://code.claude.com/docs/en/hooks\#stopfailure) StopFailure
|
|
2032
2442
|
|
|
2033
|
-
Runs instead of [Stop](https://code.claude.com/docs/en/hooks#stop) when the turn ends due to an API error.
|
|
2443
|
+
Runs instead of [Stop](https://code.claude.com/docs/en/hooks#stop) when the turn ends due to an API error. Claude Code ignores the hook’s output and exit code, apart from [`terminalSequence`](https://code.claude.com/docs/en/hooks#emit-terminal-notifications). Use this to log failures, send alerts, or take recovery actions when Claude can’t complete a response due to rate limits, authentication problems, or other API errors.
|
|
2034
2444
|
|
|
2035
2445
|
#### [](https://code.claude.com/docs/en/hooks\#stopfailure-input) StopFailure input
|
|
2036
2446
|
|
|
@@ -2038,7 +2448,7 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
2038
2448
|
|
|
2039
2449
|
| Field | Description |
|
|
2040
2450
|
| --- | --- |
|
|
2041
|
-
| `error` | Error type: `rate_limit`, `overloaded`, `authentication_failed`, `oauth_org_not_allowed`, `billing_error`, `invalid_request`, `model_not_found`, `server_error`, `max_output_tokens`, or `unknown` |
|
|
2451
|
+
| `error` | Error type: `rate_limit`, `overloaded`, `authentication_failed`, `oauth_org_not_allowed`, `account_on_hold`, `billing_error`, `invalid_request`, `model_not_found`, `server_error`, `max_output_tokens`, `cloud_credential_error`, or `unknown` |
|
|
2042
2452
|
| `error_details` | Additional details about the error, when available |
|
|
2043
2453
|
| `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"` |
|
|
2044
2454
|
|
|
@@ -2058,7 +2468,7 @@ StopFailure hooks have no decision control. They run for notification and loggin
|
|
|
2058
2468
|
|
|
2059
2469
|
### [](https://code.claude.com/docs/en/hooks\#teammateidle) TeammateIdle
|
|
2060
2470
|
|
|
2061
|
-
Runs when an [agent team](https://code.claude.com/docs/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.
|
|
2471
|
+
Runs when an [agent team](https://code.claude.com/docs/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.TeammateIdle hooks don’t support matchers and fire on every occurrence.
|
|
2062
2472
|
|
|
2063
2473
|
#### [](https://code.claude.com/docs/en/hooks\#teammateidle-input) TeammateIdle input
|
|
2064
2474
|
|
|
@@ -2080,6 +2490,7 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
2080
2490
|
| --- | --- |
|
|
2081
2491
|
| `teammate_name` | Name of the teammate that is about to go idle |
|
|
2082
2492
|
| `team_name` | Deprecated. Session-derived team name; will be removed in a future release |
|
|
2493
|
+
| `agent_id` | On this event, the [common input field](https://code.claude.com/docs/en/hooks#common-input-fields) identifies the [in-process teammate](https://code.claude.com/docs/en/agent-teams#choose-a-display-mode) that is about to go idle. May be absent. Requires Claude Code v2.1.290 or later |
|
|
2083
2494
|
|
|
2084
2495
|
#### [](https://code.claude.com/docs/en/hooks\#teammateidle-decision-control) TeammateIdle decision control
|
|
2085
2496
|
|
|
@@ -2103,14 +2514,14 @@ exit 0
|
|
|
2103
2514
|
|
|
2104
2515
|
### [](https://code.claude.com/docs/en/hooks\#configchange) ConfigChange
|
|
2105
2516
|
|
|
2106
|
-
Runs when a configuration file changes during a session. Use this to audit settings changes, enforce security policies, or block unauthorized modifications to configuration files.ConfigChange hooks
|
|
2517
|
+
Runs when a configuration file changes during a session. Use this to audit settings changes, enforce security policies, or block unauthorized modifications to configuration files.Claude Code runs ConfigChange hooks when a settings file, a managed policy file, or a skill file changes. For managed policy, it runs them only when `managed-settings.json` or a file in `managed-settings.d/` changes. It applies [server-managed settings](https://code.claude.com/docs/en/server-managed-settings) and changes to macOS managed preferences or Windows registry policy without running them. On WSL with [`wslInheritsWindowsSettings`](https://code.claude.com/docs/en/settings-reference#wslinheritswindowssettings), it also applies a changed Windows-side managed settings file on its policy poll without running them.The matcher filters on the configuration source:
|
|
2107
2518
|
|
|
2108
2519
|
| Matcher | When it fires |
|
|
2109
2520
|
| --- | --- |
|
|
2110
2521
|
| `user_settings` | `~/.claude/settings.json` changes |
|
|
2111
2522
|
| `project_settings` | `.claude/settings.json` changes |
|
|
2112
2523
|
| `local_settings` | `.claude/settings.local.json` changes |
|
|
2113
|
-
| `policy_settings` |
|
|
2524
|
+
| `policy_settings` | `managed-settings.json` or a file in `managed-settings.d/` changes |
|
|
2114
2525
|
| `skills` | A skill file in `.claude/skills/` changes |
|
|
2115
2526
|
|
|
2116
2527
|
This example logs all configuration changes for security auditing:
|
|
@@ -2155,7 +2566,7 @@ ConfigChange hooks can block configuration changes from taking effect. Use exit
|
|
|
2155
2566
|
| Field | Description |
|
|
2156
2567
|
| --- | --- |
|
|
2157
2568
|
| `decision` | `"block"` prevents the configuration change from being applied. Omit to allow the change |
|
|
2158
|
-
| `reason` |
|
|
2569
|
+
| `reason` | Accepted but never shown |
|
|
2159
2570
|
|
|
2160
2571
|
```
|
|
2161
2572
|
{
|
|
@@ -2164,11 +2575,11 @@ ConfigChange hooks can block configuration changes from taking effect. Use exit
|
|
|
2164
2575
|
}
|
|
2165
2576
|
```
|
|
2166
2577
|
|
|
2167
|
-
`policy_settings` changes can’t be blocked. Hooks still fire for `policy_settings` sources, so you can use them
|
|
2578
|
+
`policy_settings` changes can’t be blocked. Hooks still fire for `policy_settings` sources when a managed settings file on the machine changes, so you can use them to log those edits, but any blocking decision is ignored. This ensures enterprise-managed settings always take effect. Claude Code doesn’t run `ConfigChange` hooks when [server-managed settings](https://code.claude.com/docs/en/server-managed-settings) arrive or refresh.Claude Code acts on the blocking decision from a ConfigChange hook’s JSON output and discards `systemMessage` and `continue`. A blocked change surfaces no message to you or to Claude, whether you block with `reason` or with stderr on exit 2. Claude Code only writes a line to the debug log.
|
|
2168
2579
|
|
|
2169
2580
|
### [](https://code.claude.com/docs/en/hooks\#cwdchanged) CwdChanged
|
|
2170
2581
|
|
|
2171
|
-
Runs when the
|
|
2582
|
+
Runs when a shell command in the main conversation changes the working directory, 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](https://code.claude.com/docs/en/hooks#filechanged) for tools like [direnv](https://direnv.net/) that manage per-directory environment.CwdChanged hooks have access to [`CLAUDE_ENV_FILE`](https://code.claude.com/docs/en/hooks#persist-environment-variables). Variables written to that file persist into subsequent Bash commands until the next CwdChanged event, when Claude Code clears them.CwdChanged doesn’t support matchers and fires on every occurrence.
|
|
2172
2583
|
|
|
2173
2584
|
#### [](https://code.claude.com/docs/en/hooks\#cwdchanged-input) CwdChanged input
|
|
2174
2585
|
|
|
@@ -2193,34 +2604,104 @@ In addition to the [JSON output fields](https://code.claude.com/docs/en/hooks#js
|
|
|
2193
2604
|
| --- | --- |
|
|
2194
2605
|
| `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 |
|
|
2195
2606
|
|
|
2196
|
-
CwdChanged hooks have no decision control. They can’t block the directory change.
|
|
2607
|
+
CwdChanged hooks have no decision control. They can’t block the directory change.Claude Code reads `watchPaths` and `systemMessage` from their JSON output and discards `continue`. In interactive sessions, it shows the `systemMessage` as a brief terminal notification. The message doesn’t reach the SDK message stream.
|
|
2197
2608
|
|
|
2198
|
-
### [](https://code.claude.com/docs/en/hooks\#
|
|
2609
|
+
### [](https://code.claude.com/docs/en/hooks\#directoryadded) DirectoryAdded
|
|
2199
2610
|
|
|
2200
|
-
Runs
|
|
2611
|
+
Runs after you add a working directory mid-session with the `/add-dir` command, or after an SDK client adds one with the `register_repo_root` control request. Use this to prepare a newly added repository, for example by installing its dependencies.Claude Code doesn’t fire this event when:
|
|
2201
2612
|
|
|
2202
|
-
-
|
|
2203
|
-
-
|
|
2613
|
+
- You pass a directory with the `--add-dir` startup flag; [SessionStart](https://code.claude.com/docs/en/hooks#sessionstart) covers those directories
|
|
2614
|
+
- You add a directory on the `/permissions` Workspace tab
|
|
2615
|
+
- You add a directory that is already a working directory or inside one
|
|
2204
2616
|
|
|
2205
|
-
|
|
2617
|
+
Claude Code fires DirectoryAdded after refreshing sandbox and permission state, so sandboxed tools already see the new directory when your hook runs. Hook commands themselves run unsandboxed.Claude Code doesn’t wait for the hook: the add completes immediately, and the hook runs in the background with the 600-second default timeout.The matcher filters on how the directory was added:
|
|
2206
2618
|
|
|
2207
|
-
|
|
2619
|
+
| Matcher | When it fires |
|
|
2620
|
+
| --- | --- |
|
|
2621
|
+
| `slash_command` | You add a directory with `/add-dir` |
|
|
2622
|
+
| `register_repo_root` | An SDK client adds a directory with the `register_repo_root` control request |
|
|
2208
2623
|
|
|
2209
|
-
|
|
2624
|
+
#### [](https://code.claude.com/docs/en/hooks\#directoryadded-input) DirectoryAdded input
|
|
2625
|
+
|
|
2626
|
+
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields), DirectoryAdded hooks receive `directory` and `source`.
|
|
2210
2627
|
|
|
2211
2628
|
| Field | Description |
|
|
2212
2629
|
| --- | --- |
|
|
2213
|
-
| `
|
|
2214
|
-
| `
|
|
2630
|
+
| `directory` | Absolute path of the directory that was added |
|
|
2631
|
+
| `source` | How the directory was added, `"slash_command"` for `/add-dir` or `"register_repo_root"` for the SDK control request |
|
|
2215
2632
|
|
|
2216
2633
|
```
|
|
2217
2634
|
{
|
|
2218
2635
|
"session_id": "abc123",
|
|
2219
2636
|
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
|
|
2220
2637
|
"cwd": "/Users/my-project",
|
|
2221
|
-
"hook_event_name": "
|
|
2222
|
-
"
|
|
2223
|
-
"
|
|
2638
|
+
"hook_event_name": "DirectoryAdded",
|
|
2639
|
+
"directory": "/Users/my-other-repo",
|
|
2640
|
+
"source": "slash_command"
|
|
2641
|
+
}
|
|
2642
|
+
```
|
|
2643
|
+
|
|
2644
|
+
DirectoryAdded hooks have no decision control. They can’t block the add, which has already completed when the hook runs. Claude Code discards the `continue` field from their JSON output and surfaces the rest differently per source:
|
|
2645
|
+
|
|
2646
|
+
- `slash_command`: Claude Code delivers the hook’s `systemMessage` to Claude as context on the next conversation turn, rather than showing it to you. A count of failed hooks appears in the transcript. Full failure output goes to the debug log
|
|
2647
|
+
- `register_repo_root`: Claude Code writes `systemMessage` output and failure output to the debug log only
|
|
2648
|
+
|
|
2649
|
+
### [](https://code.claude.com/docs/en/hooks\#filechanged) FileChanged
|
|
2650
|
+
|
|
2651
|
+
Runs when a watched file changes on disk. Claude Code detects changes with a filesystem watcher, not by inspecting tool calls, so it runs the hook no matter what changed the file: an `Edit` or `Write` tool call, a script Claude runs with `Bash`, or a process outside Claude Code entirely. A common use is reloading environment variables when project configuration files change.The `matcher` for this event serves two roles:
|
|
2652
|
+
|
|
2653
|
+
- **Build the watch list**: the value is split on `|` and each segment is registered as a literal filename in the working directory, so `".envrc|.env"` watches exactly those two files. Regex patterns are not useful here: a value like `^\.env` would watch a file literally named `^\.env`.
|
|
2654
|
+
- **Filter which hooks run**: when a watched file changes, the same value filters which hook groups run using the standard [matcher rules](https://code.claude.com/docs/en/hooks#matcher-patterns) against the changed file’s basename.
|
|
2655
|
+
|
|
2656
|
+
This example normalizes line endings in `data.csv` after any change, including a `Bash` command or an external script rewriting the file:
|
|
2657
|
+
|
|
2658
|
+
```
|
|
2659
|
+
{
|
|
2660
|
+
"hooks": {
|
|
2661
|
+
"FileChanged": [\
|
|
2662
|
+
{\
|
|
2663
|
+
"matcher": "data.csv",\
|
|
2664
|
+
"hooks": [\
|
|
2665
|
+
{\
|
|
2666
|
+
"type": "command",\
|
|
2667
|
+
"command": "/path/to/normalize-line-endings.sh"\
|
|
2668
|
+
}\
|
|
2669
|
+
]\
|
|
2670
|
+
}\
|
|
2671
|
+
]
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
```
|
|
2675
|
+
|
|
2676
|
+
The hook reads the changed file’s absolute path from the `file_path` field of the [JSON input](https://code.claude.com/docs/en/hooks#filechanged-input) on stdin. Its `grep` guard tests for the same thing `perl` removes, a CR at the end of a line, so the run after a normalization exits without touching the file. A looser guard loops forever, because `perl -i` rewrites the file even when it substitutes nothing and Claude Code runs the hook again after every rewrite. Save this script at `/path/to/normalize-line-endings.sh` and make it executable:
|
|
2677
|
+
|
|
2678
|
+
```
|
|
2679
|
+
#!/bin/bash
|
|
2680
|
+
FILE=$(jq -r .file_path)
|
|
2681
|
+
if grep -q $'\r$' "$FILE"; then
|
|
2682
|
+
perl -pi -e 's/\r$//' "$FILE"
|
|
2683
|
+
fi
|
|
2684
|
+
```
|
|
2685
|
+
|
|
2686
|
+
To confirm the hook works, ask Claude to append a CRLF line to `data.csv` with a `Bash` command. Claude Code runs the hook and the file ends up with LF endings.To watch files you can’t name up front, return [`watchPaths`](https://code.claude.com/docs/en/hooks#filechanged-output) from a hook to update the watch list dynamically. Claude Code starts the watcher only when something names a file to watch, so seed the list with a FileChanged group whose matcher names at least one file, or with a [SessionStart](https://code.claude.com/docs/en/hooks#sessionstart-decision-control) or [CwdChanged](https://code.claude.com/docs/en/hooks#cwdchanged) hook that returns `watchPaths`. The matcher still filters which hook groups run when a watched file changes, so give the group that handles dynamic paths an omitted matcher, which matches every watched file and adds nothing to the watch list. A `"*"` matcher also matches every file, but Claude Code registers it in the watch list like any other value, as a literal file named `*`.FileChanged hooks have access to [`CLAUDE_ENV_FILE`](https://code.claude.com/docs/en/hooks#persist-environment-variables). Variables written to that file persist into subsequent Bash commands until the next [CwdChanged](https://code.claude.com/docs/en/hooks#cwdchanged) event, when Claude Code clears them.
|
|
2687
|
+
|
|
2688
|
+
#### [](https://code.claude.com/docs/en/hooks\#filechanged-input) FileChanged input
|
|
2689
|
+
|
|
2690
|
+
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields), FileChanged hooks receive `file_path` and `event`.
|
|
2691
|
+
|
|
2692
|
+
| Field | Description |
|
|
2693
|
+
| --- | --- |
|
|
2694
|
+
| `file_path` | Absolute path to the file that changed |
|
|
2695
|
+
| `event` | What happened: `"change"` for a modified file, `"add"` for a created file, or `"unlink"` for a deleted file |
|
|
2696
|
+
|
|
2697
|
+
```
|
|
2698
|
+
{
|
|
2699
|
+
"session_id": "abc123",
|
|
2700
|
+
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
|
|
2701
|
+
"cwd": "/Users/my-project",
|
|
2702
|
+
"hook_event_name": "FileChanged",
|
|
2703
|
+
"file_path": "/Users/my-project/.envrc",
|
|
2704
|
+
"event": "change"
|
|
2224
2705
|
}
|
|
2225
2706
|
```
|
|
2226
2707
|
|
|
@@ -2232,11 +2713,20 @@ In addition to the [JSON output fields](https://code.claude.com/docs/en/hooks#js
|
|
|
2232
2713
|
| --- | --- |
|
|
2233
2714
|
| `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 |
|
|
2234
2715
|
|
|
2235
|
-
FileChanged hooks have no decision control. They can’t block the file change from occurring.
|
|
2716
|
+
FileChanged hooks have no decision control. They can’t block the file change from occurring.Claude Code reads `watchPaths` and `systemMessage` from their JSON output and discards `continue`. In interactive sessions, it shows the `systemMessage` as a brief terminal notification. The message doesn’t reach the SDK message stream.
|
|
2236
2717
|
|
|
2237
2718
|
### [](https://code.claude.com/docs/en/hooks\#worktreecreate) WorktreeCreate
|
|
2238
2719
|
|
|
2239
|
-
Runs when
|
|
2720
|
+
Runs when Claude Code creates a worktree in cases such as these:
|
|
2721
|
+
|
|
2722
|
+
- You start a session with `claude --worktree`
|
|
2723
|
+
- Claude creates a worktree during a session and switches into it with the [`EnterWorktree`](https://code.claude.com/docs/en/tools-reference) tool
|
|
2724
|
+
- A [subagent uses `isolation: "worktree"`](https://code.claude.com/docs/en/sub-agents#choose-the-subagent-scope)
|
|
2725
|
+
- A [workflow](https://code.claude.com/docs/en/workflows#migrate-many-files-in-parallel) runs an agent in its own worktree
|
|
2726
|
+
- Claude Code isolates a [background session](https://code.claude.com/docs/en/agent-view#how-file-edits-are-isolated) in its own worktree
|
|
2727
|
+
- [`claude remote-control`](https://code.claude.com/docs/en/remote-control#start-a-remote-control-session) in `worktree` mode starts a session in its own worktree
|
|
2728
|
+
|
|
2729
|
+
When Claude runs `git worktree add` through the Bash tool rather than calling `EnterWorktree`, your WorktreeCreate hook doesn’t run. To inspect or block that command, add a [`PreToolUse`](https://code.claude.com/docs/en/hooks#pretooluse) hook with the `Bash` matcher.By default Claude Code creates the isolated working copy with `git worktree`. Configuring a WorktreeCreate hook replaces that default git behavior, letting you use a different version control system like SVN, Perforce, or Mercurial.Because the hook replaces the default behavior entirely, [`.worktreeinclude`](https://code.claude.com/docs/en/worktrees#copy-gitignored-files-into-worktrees) is not processed. If you need to copy local configuration files like `.env` into the new worktree, do it inside your hook script.The hook must return the path to the created worktree directory. Claude Code uses this path as the working directory for the isolated session. See [WorktreeCreate output](https://code.claude.com/docs/en/hooks#worktreecreate-output) for how each hook type returns the path.Claude Code acts on the hook’s success and the returned path, and discards `systemMessage` and `continue`.This example creates an SVN working copy and prints the path for Claude Code to use. Replace the repository URL with your own:
|
|
2240
2730
|
|
|
2241
2731
|
```
|
|
2242
2732
|
{
|
|
@@ -2278,11 +2768,29 @@ WorktreeCreate hooks don’t use the standard allow/block decision model. Instea
|
|
|
2278
2768
|
- **Command hooks** (`type: "command"`): print the path as the last non-empty line of stdout. Claude Code strips ANSI escape codes before reading that line, so shell startup banners printed before your `echo` are ignored. Redirect any other hook output to stderr.
|
|
2279
2769
|
- **HTTP hooks** (`type: "http"`): return `{ "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } }` in the response body.
|
|
2280
2770
|
|
|
2281
|
-
If the hook fails or produces no path, worktree creation fails with an error.Claude Code resolves a relative path against the directory the hook ran in. If the resulting path isn’t a directory Claude Code can enter, the session prints an error naming the path and exits with code 1.
|
|
2771
|
+
If the hook fails or produces no path, worktree creation fails with an error.Claude Code resolves a relative path against the directory the hook ran in, collapsing any `.` or `..` segments in it. If the resulting path isn’t a directory Claude Code can enter, the session prints an error naming the path and exits with code 1.Claude Code refuses an absolute path that contains `.` or `..` segments, and any path that passes through a symlink below the repository root, because a symlink committed to the repository could redirect the worktree outside it. The error names the rejected component. Return a normalized path that doesn’t pass through a symlink inside the repository. Before v2.1.216, worktree creation followed the hook’s path without this screening.
|
|
2282
2772
|
|
|
2283
2773
|
### [](https://code.claude.com/docs/en/hooks\#worktreeremove) WorktreeRemove
|
|
2284
2774
|
|
|
2285
|
-
Runs when
|
|
2775
|
+
Runs when Claude Code cleans up a worktree that your [`WorktreeCreate`](https://code.claude.com/docs/en/hooks#worktreecreate) hook created. The event fires when:
|
|
2776
|
+
|
|
2777
|
+
- You exit an interactive [worktree session](https://code.claude.com/docs/en/worktrees#start-claude-in-a-worktree) and choose to remove the worktree when Claude Code prompts you
|
|
2778
|
+
- You exit an interactive worktree session you haven’t [named](https://code.claude.com/docs/en/sessions#name-your-sessions), Claude Code finds no changed or untracked files, and it removes the worktree without prompting you
|
|
2779
|
+
- You delete a [background session](https://code.claude.com/docs/en/agent-view#what-deleting-a-session-removes) that runs in the worktree
|
|
2780
|
+
- You ask Claude to exit and remove the worktree during a session, and Claude removes it with the [`ExitWorktree`](https://code.claude.com/docs/en/tools-reference) tool
|
|
2781
|
+
- A session that [`claude remote-control`](https://code.claude.com/docs/en/remote-control#start-a-remote-control-session) started in `worktree` mode ends without crashing, and Claude Code finds no changed or untracked files and no new commits in its worktree
|
|
2782
|
+
- You stop `claude remote-control` while sessions it started in `worktree` mode are still running, and Claude Code finds no changed or untracked files and no new commits in their worktrees
|
|
2783
|
+
|
|
2784
|
+
When a subagent or a workflow agent finishes, Claude Code keeps the worktree your WorktreeCreate hook made for it and doesn’t run your WorktreeRemove hook. Remove those worktrees yourself when the work is done.Claude Code doesn’t prompt you before your hook runs for an `ExitWorktree` call or a `claude remote-control` cleanup. `ExitWorktree` refuses to remove a hook-created worktree unless the call passes [`discard_changes: true`](https://code.claude.com/docs/en/agent-sdk/typescript#exitworktree), which makes your hook the last check on that route.Claude Code uses git to look for changed or untracked files, so it finds none in a worktree that isn’t a git checkout or inside one, even when the directory holds uncommitted work. Check for that work in your WorktreeRemove hook before it deletes anything.For git-based worktrees, Claude Code handles cleanup automatically with `git worktree remove`. If you configured a WorktreeCreate hook, pair it with a WorktreeRemove hook to control cleanup:
|
|
2785
|
+
|
|
2786
|
+
- **No WorktreeRemove hook**: Claude Code falls back to git for a removal at exit or through `ExitWorktree`, and keeps the worktree when `claude remote-control` cleans up:
|
|
2787
|
+
- **You exit a worktree session, or Claude calls `ExitWorktree`**: Claude Code removes the path your WorktreeCreate hook returned as `git worktree remove --force` would, so a worktree git recognizes is removed. A worktree git doesn’t recognize, for example one your hook created with a non-git version control system, stays on disk.
|
|
2788
|
+
- **`claude remote-control` cleans up a session’s worktree**: the worktree stays on disk, and the terminal running `claude remote-control` shows `worktree removal failed, kept: <path>`.
|
|
2789
|
+
- **You delete a background session**: see [What deleting a session removes](https://code.claude.com/docs/en/agent-view#what-deleting-a-session-removes).
|
|
2790
|
+
- **Hook exits 0**: the worktree counts as removed. Claude Code reads nothing else from the hook, so make sure your hook deleted the directory.
|
|
2791
|
+
- **Hook exits non-zero**: the removal fails if the directory at `worktree_path` still exists afterward, and the worktree stays on disk with no git fallback. A hook that deleted the directory before exiting non-zero counts as removed. For how the failure is reported, see [WorktreeRemove input](https://code.claude.com/docs/en/hooks#worktreeremove-input).
|
|
2792
|
+
|
|
2793
|
+
Claude Code never deletes a branch belonging to a hook-created worktree, because it only knows the path your WorktreeCreate hook returned. If your WorktreeCreate hook creates a branch, delete it in your WorktreeRemove hook.Claude Code discards a WorktreeRemove hook’s [JSON output fields](https://code.claude.com/docs/en/hooks#json-output), such as `systemMessage` and `continue`.For a background-session delete, Claude Code verifies the stored worktree path before running the hook and refuses a path that is a symlink or passes through one below the repository root. On that route, the hook runs for a worktree that still contains files only when you confirm the delete in [agent view](https://code.claude.com/docs/en/agent-view#what-deleting-a-session-removes); for such a worktree, [`claude rm`](https://code.claude.com/docs/en/agent-view#manage-sessions-from-the-shell) keeps the session and worktree instead. Before v2.1.216, the hook ran on the stored path without these checks.Claude Code passes the path returned by WorktreeCreate as `worktree_path` in the hook input. This example reads that path and removes the directory:
|
|
2286
2794
|
|
|
2287
2795
|
```
|
|
2288
2796
|
{
|
|
@@ -2315,7 +2823,11 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
2315
2823
|
}
|
|
2316
2824
|
```
|
|
2317
2825
|
|
|
2318
|
-
WorktreeRemove
|
|
2826
|
+
A WorktreeRemove hook’s exit code decides the outcome. When a hook exits non-zero and the directory at `worktree_path` still exists afterward, the removal fails:
|
|
2827
|
+
|
|
2828
|
+
- The worktree stays on disk, and the hook’s command and stderr go to the [debug log](https://code.claude.com/docs/en/hooks#debug-hooks).
|
|
2829
|
+
- If you were deleting a background session, the session stays too. The refusal message in [agent view](https://code.claude.com/docs/en/agent-view#what-deleting-a-session-removes) reports how the hook ended, such as `exited 1`, quotes the start of its stderr, and says whether deleting the session again removes the directory anyway.
|
|
2830
|
+
- If `claude remote-control` was cleaning up a session’s worktree, the terminal running it shows `worktree removal failed, kept: <path>`.
|
|
2319
2831
|
|
|
2320
2832
|
### [](https://code.claude.com/docs/en/hooks\#precompact) PreCompact
|
|
2321
2833
|
|
|
@@ -2324,13 +2836,13 @@ Runs before Claude Code is about to run a compact operation.The matcher value in
|
|
|
2324
2836
|
| Matcher | When it fires |
|
|
2325
2837
|
| --- | --- |
|
|
2326
2838
|
| `manual` | `/compact` |
|
|
2327
|
-
| `auto` | Auto-compact when the
|
|
2839
|
+
| `auto` | Auto-compact when the conversation reaches the [auto-compact window](https://code.claude.com/docs/en/model-config#set-the-auto-compact-window) |
|
|
2328
2840
|
|
|
2329
|
-
Exit with code 2 to block compaction. For a manual `/compact`, the stderr message is shown to the user. You can also block by returning JSON with `"decision": "block"`.Blocking automatic compaction has different effects depending on when it fires. If compaction was triggered proactively before the context limit, Claude Code skips it and the conversation continues uncompacted. If compaction was triggered to recover from a context-limit error already returned by the API, the underlying error surfaces and the current request fails.
|
|
2841
|
+
Exit with code 2 to block compaction. For a manual `/compact`, the stderr message is shown to the user. You can also block by returning JSON with `"decision": "block"`.Blocking automatic compaction has different effects depending on when it fires. If compaction was triggered proactively before the context limit, Claude Code skips it and the conversation continues uncompacted. If compaction was triggered to recover from a context-limit error already returned by the API, the underlying error surfaces and the current request fails.Claude Code discards a PreCompact hook’s `systemMessage` and `continue` fields.
|
|
2330
2842
|
|
|
2331
2843
|
#### [](https://code.claude.com/docs/en/hooks\#precompact-input) PreCompact input
|
|
2332
2844
|
|
|
2333
|
-
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields), PreCompact hooks receive `trigger` and `custom_instructions`. For `manual`, `custom_instructions` contains what the user passes into `/compact
|
|
2845
|
+
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields), PreCompact hooks receive `trigger` and `custom_instructions`. For `manual`, `custom_instructions` contains what the user passes into `/compact` and is `null` when they pass nothing. For `auto`, `custom_instructions` is `null`.
|
|
2334
2846
|
|
|
2335
2847
|
```
|
|
2336
2848
|
{
|
|
@@ -2339,18 +2851,18 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
2339
2851
|
"cwd": "/Users/...",
|
|
2340
2852
|
"hook_event_name": "PreCompact",
|
|
2341
2853
|
"trigger": "manual",
|
|
2342
|
-
"custom_instructions":
|
|
2854
|
+
"custom_instructions": null
|
|
2343
2855
|
}
|
|
2344
2856
|
```
|
|
2345
2857
|
|
|
2346
2858
|
### [](https://code.claude.com/docs/en/hooks\#postcompact) PostCompact
|
|
2347
2859
|
|
|
2348
|
-
Runs 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.The same matcher values apply as for `PreCompact`:
|
|
2860
|
+
Runs 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. Claude Code discards a PostCompact hook’s `systemMessage` and `continue` fields.The same matcher values apply as for `PreCompact`:
|
|
2349
2861
|
|
|
2350
2862
|
| Matcher | When it fires |
|
|
2351
2863
|
| --- | --- |
|
|
2352
2864
|
| `manual` | After `/compact` |
|
|
2353
|
-
| `auto` | After auto-compact when the
|
|
2865
|
+
| `auto` | After auto-compact when the conversation reaches the [auto-compact window](https://code.claude.com/docs/en/model-config#set-the-auto-compact-window) |
|
|
2354
2866
|
|
|
2355
2867
|
#### [](https://code.claude.com/docs/en/hooks\#postcompact-input) PostCompact input
|
|
2356
2868
|
|
|
@@ -2369,6 +2881,187 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
2369
2881
|
|
|
2370
2882
|
PostCompact hooks have no decision control. They can’t affect the compaction result but can perform follow-up tasks.
|
|
2371
2883
|
|
|
2884
|
+
### [](https://code.claude.com/docs/en/hooks\#premodelswitch) PreModelSwitch
|
|
2885
|
+
|
|
2886
|
+
Runs before Claude Code applies a model switch that you or a client requested. Use it to block a switch, require confirmation, or show what the switch will cost before it happens.PreModelSwitch requires Claude Code v2.1.251 or later. Claude Code runs it for these requests:
|
|
2887
|
+
|
|
2888
|
+
- `/model <name>` and the `/model` picker
|
|
2889
|
+
- The `Option+P` or `Alt+P` model picker
|
|
2890
|
+
- The Model setting in `/config`
|
|
2891
|
+
- Turning on [fast mode](https://code.claude.com/docs/en/fast-mode) when that changes the session’s model
|
|
2892
|
+
- A `set_model` request, or a model change in an `apply_flag_settings` request, from an [Agent SDK](https://code.claude.com/docs/en/agent-sdk/typescript#query-object) host or [Remote Control](https://code.claude.com/docs/en/remote-control)
|
|
2893
|
+
|
|
2894
|
+
Claude Code doesn’t run PreModelSwitch hooks for switches it makes on its own, such as an [automatic model fallback](https://code.claude.com/docs/en/model-config#automatic-model-fallback) or restoring the model when you resume a session. Those changes reach [PostModelSwitch](https://code.claude.com/docs/en/hooks#postmodelswitch) only.Claude Code compares the matcher against the canonical name of the model the session is switching to, ignoring any `[1m]` suffix. An alias such as `opus`, a dated model ID, and a provider-specific ID such as an Amazon Bedrock model ID all match the one canonical name they resolve to, so `claude-opus-5` covers every spelling of Opus 5.When Claude Code can’t determine a canonical name for the target, for example a custom model ID that only your [LLM gateway](https://code.claude.com/docs/en/llm-gateway) knows, it runs every PreModelSwitch hook regardless of matcher. A hook that blocks should therefore check `to_model` from its input rather than rely on the matcher alone.Write the matcher as an exact name, a `|`-separated list such as `claude-opus-4-6|claude-opus-5`, or a regular expression such as `.*opus.*`. This example uses an exact-name matcher and also checks `to_model` from the hook input, so it refuses a switch to Opus 4.6 by exiting with code 2 and lets any other target through:
|
|
2895
|
+
|
|
2896
|
+
- macOS/Linux
|
|
2897
|
+
|
|
2898
|
+
- Windows (PowerShell)
|
|
2899
|
+
|
|
2900
|
+
|
|
2901
|
+
The command checks `to_model` with `jq`:
|
|
2902
|
+
|
|
2903
|
+
```
|
|
2904
|
+
{
|
|
2905
|
+
"hooks": {
|
|
2906
|
+
"PreModelSwitch": [\
|
|
2907
|
+
{\
|
|
2908
|
+
"matcher": "claude-opus-4-6",\
|
|
2909
|
+
"hooks": [\
|
|
2910
|
+
{\
|
|
2911
|
+
"type": "command",\
|
|
2912
|
+
"command": "jq -e '.to_model | test(\"opus-4-6\")' > /dev/null && { echo 'Opus 4.6 is retired for this project. Use a newer model.' >&2; exit 2; }; exit 0"\
|
|
2913
|
+
}\
|
|
2914
|
+
]\
|
|
2915
|
+
}\
|
|
2916
|
+
]
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
```
|
|
2920
|
+
|
|
2921
|
+
Register a command hook that runs a script through PowerShell:
|
|
2922
|
+
|
|
2923
|
+
```
|
|
2924
|
+
{
|
|
2925
|
+
"hooks": {
|
|
2926
|
+
"PreModelSwitch": [\
|
|
2927
|
+
{\
|
|
2928
|
+
"matcher": "claude-opus-4-6",\
|
|
2929
|
+
"hooks": [\
|
|
2930
|
+
{\
|
|
2931
|
+
"type": "command",\
|
|
2932
|
+
"command": "powershell.exe",\
|
|
2933
|
+
"args": [\
|
|
2934
|
+
"-NoProfile",\
|
|
2935
|
+
"-ExecutionPolicy",\
|
|
2936
|
+
"Bypass",\
|
|
2937
|
+
"-File",\
|
|
2938
|
+
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-opus-46.ps1"\
|
|
2939
|
+
]\
|
|
2940
|
+
}\
|
|
2941
|
+
]\
|
|
2942
|
+
}\
|
|
2943
|
+
]
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
```
|
|
2947
|
+
|
|
2948
|
+
Save this script to `.claude/hooks/block-opus-46.ps1` in your project:
|
|
2949
|
+
|
|
2950
|
+
```
|
|
2951
|
+
$hookInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
2952
|
+
if ($hookInput.to_model -match 'opus-4-6') {
|
|
2953
|
+
[Console]::Error.WriteLine('Opus 4.6 is retired for this project. Use a newer model.')
|
|
2954
|
+
exit 2
|
|
2955
|
+
}
|
|
2956
|
+
exit 0
|
|
2957
|
+
```
|
|
2958
|
+
|
|
2959
|
+
To confirm the hook works, run `/model claude-opus-4-6` from a session running a different model. Claude Code keeps the current model and reports that a PreModelSwitch hook blocked the switch, with your message as the reason.
|
|
2960
|
+
|
|
2961
|
+
#### [](https://code.claude.com/docs/en/hooks\#premodelswitch-input) PreModelSwitch input
|
|
2962
|
+
|
|
2963
|
+
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields), PreModelSwitch hooks receive the fields in this table. The last five describe what re-sending the conversation to the new model costs, so a hook can show that figure before the switch happens.
|
|
2964
|
+
|
|
2965
|
+
| Field | Type | Description |
|
|
2966
|
+
| --- | --- | --- |
|
|
2967
|
+
| `from_model` | string | Model ID the switch changes from |
|
|
2968
|
+
| `to_model` | string | Model ID the switch changes to. The matcher compares against this model’s canonical name |
|
|
2969
|
+
| `requested_model` | string or `null` | The model the request named: an alias such as `opus`, a full model ID, or `null` when the request was for the default model |
|
|
2970
|
+
| `source` | string | Where the request came from: `"command"` for `/model <name>`, the Model setting in `/config`, or turning on fast mode; `"picker"` for a model picker; `"sdk"` for a `set_model` request, or a model change in an `apply_flag_settings` request, from an Agent SDK host or Remote Control |
|
|
2971
|
+
| `context_tokens` | number | Tokens the next request re-sends as its prompt: the input, cache read, cache creation, and output tokens of the last response in the main conversation, combined. `0` before the first response |
|
|
2972
|
+
| `prompt_cache_warm` | boolean | Whether the current model’s prompt cache is likely still warm, meaning the switch forfeits it |
|
|
2973
|
+
| `cache_ttl` | string | [Prompt cache lifetime](https://code.claude.com/docs/en/prompt-caching#cache-lifetime) Claude Code requests for this session: `"5m"` or `"1h"` |
|
|
2974
|
+
| `estimated_cache_write_usd` | number | Estimated cost in US dollars of writing `context_tokens` to the prompt cache on `to_model` at the `cache_ttl` rate, excluding the next response. The server may not need to re-cache the whole context, so treat it as an estimate |
|
|
2975
|
+
| `pricing` | string | How Claude Code priced `estimated_cache_write_usd`: `"configured"` at your organization’s own rates when it has configured them, `"catalog"` at list price, or `"default"` when `to_model` has no known price and Claude Code assumed a default rate |
|
|
2976
|
+
|
|
2977
|
+
This example shows the input for `/model opus` in a session running Sonnet 5:
|
|
2978
|
+
|
|
2979
|
+
```
|
|
2980
|
+
{
|
|
2981
|
+
"session_id": "abc123",
|
|
2982
|
+
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
|
|
2983
|
+
"cwd": "/Users/...",
|
|
2984
|
+
"hook_event_name": "PreModelSwitch",
|
|
2985
|
+
"from_model": "claude-sonnet-5",
|
|
2986
|
+
"to_model": "claude-opus-5",
|
|
2987
|
+
"requested_model": "opus",
|
|
2988
|
+
"source": "command",
|
|
2989
|
+
"context_tokens": 182340,
|
|
2990
|
+
"prompt_cache_warm": true,
|
|
2991
|
+
"cache_ttl": "5m",
|
|
2992
|
+
"estimated_cache_write_usd": 1.1396,
|
|
2993
|
+
"pricing": "catalog"
|
|
2994
|
+
}
|
|
2995
|
+
```
|
|
2996
|
+
|
|
2997
|
+
#### [](https://code.claude.com/docs/en/hooks\#premodelswitch-decision-control) PreModelSwitch decision control
|
|
2998
|
+
|
|
2999
|
+
`PreModelSwitch` hooks can cancel the switch, ask the user to confirm it, or let it proceed. Exit code 2 or a top-level `decision: "block"` cancels the switch.For finer control, return `permissionDecision` and `permissionDecisionReason` in a `hookSpecificOutput` object, as on [PreToolUse](https://code.claude.com/docs/en/hooks#pretooluse-decision-control). `PreModelSwitch` accepts `"allow"`, `"deny"`, and `"ask"`. It doesn’t accept `"defer"`, `updatedInput`, or `additionalContext`. The table below describes both fields:
|
|
3000
|
+
|
|
3001
|
+
| Field | Description |
|
|
3002
|
+
| --- | --- |
|
|
3003
|
+
| `permissionDecision` | `"allow"` proceeds and skips the [confirmation Claude Code shows while the prompt cache is warm](https://code.claude.com/docs/en/prompt-caching#switching-models). `"deny"` cancels the switch. `"ask"` prompts the user to confirm it |
|
|
3004
|
+
| `permissionDecisionReason` | For `"deny"`, shown to the user as the reason the switch was blocked, or returned as the error for a `set_model` request. For `"ask"`, shown in the confirmation prompt. Ignored for `"allow"` |
|
|
3005
|
+
|
|
3006
|
+
Only `/model` in an interactive session can show the `"ask"` prompt. On every other surface, including non-interactive mode with the `-p` flag, `/config`, and `set_model` requests, Claude Code treats `"ask"` as a refusal.This example asks the user to confirm and quotes the token count from `context_tokens`:
|
|
3007
|
+
|
|
3008
|
+
```
|
|
3009
|
+
{
|
|
3010
|
+
"hookSpecificOutput": {
|
|
3011
|
+
"hookEventName": "PreModelSwitch",
|
|
3012
|
+
"permissionDecision": "ask",
|
|
3013
|
+
"permissionDecisionReason": "Switching now re-sends about 180k tokens to the new model. Continue?"
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
```
|
|
3017
|
+
|
|
3018
|
+
When multiple PreModelSwitch hooks return different decisions, precedence is `deny` \> `ask` \> `allow`.Claude Code shows the user any `systemMessage` your hook returns regardless of the decision, so a cost-report hook can return `{"systemMessage": "..."}` and exit 0.A PreModelSwitch hook that doesn’t respond before its timeout blocks the switch. For what a timeout does on other events, see [Timeouts](https://code.claude.com/docs/en/hooks#timeouts). The default timeout for this event is 30 seconds. `PreModelSwitch` runs `command`, `http`, and `mcp_tool` hooks only, so the `prompt` and `agent` defaults don’t apply.A hook that exits with a code other than 0 or 2 and prints no JSON decision is a non-blocking error, as described under [Other exit codes](https://code.claude.com/docs/en/hooks#other-exit-codes).
|
|
3019
|
+
|
|
3020
|
+
### [](https://code.claude.com/docs/en/hooks\#postmodelswitch) PostModelSwitch
|
|
3021
|
+
|
|
3022
|
+
Runs after the session’s model changes. Use it to give Claude model-specific guidance without editing every CLAUDE.md, for example an organization-wide instruction that applies on certain models.PostModelSwitch requires Claude Code v2.1.251 or later. It can’t block, because the model has already changed. Claude Code runs PostModelSwitch hooks after any of these changes:
|
|
3023
|
+
|
|
3024
|
+
- A switch you or a client requested
|
|
3025
|
+
- An [automatic model fallback](https://code.claude.com/docs/en/model-config#automatic-model-fallback), which changes the session’s model
|
|
3026
|
+
- A setting such as [`opusplan`](https://code.claude.com/docs/en/model-config#opusplan-model-setting) entering or leaving plan mode
|
|
3027
|
+
- Claude Code restoring the model when you resume a session
|
|
3028
|
+
|
|
3029
|
+
Claude Code doesn’t run PostModelSwitch hooks when a model from a [fallback model chain](https://code.claude.com/docs/en/model-config#fallback-model-chains) serves a turn, because that substitution lasts one turn and leaves the session’s model unchanged.The matcher follows the same rules as [PreModelSwitch](https://code.claude.com/docs/en/hooks#premodelswitch): Claude Code compares it against the canonical name of the model the session switched to.This example adds guidance whenever the session’s model changes to any Opus model:
|
|
3030
|
+
|
|
3031
|
+
```
|
|
3032
|
+
{
|
|
3033
|
+
"hooks": {
|
|
3034
|
+
"PostModelSwitch": [\
|
|
3035
|
+
{\
|
|
3036
|
+
"matcher": ".*opus.*",\
|
|
3037
|
+
"hooks": [\
|
|
3038
|
+
{\
|
|
3039
|
+
"type": "command",\
|
|
3040
|
+
"command": "echo 'On Opus, delegate implementation work to subagents and keep this conversation for planning and review.'"\
|
|
3041
|
+
}\
|
|
3042
|
+
]\
|
|
3043
|
+
}\
|
|
3044
|
+
]
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
```
|
|
3048
|
+
|
|
3049
|
+
To confirm the hook works, switch to an Opus model from a session running a different model, for example run `/model opus` from a Sonnet session, then ask Claude what guidance it has about the current model.
|
|
3050
|
+
|
|
3051
|
+
#### [](https://code.claude.com/docs/en/hooks\#postmodelswitch-input) PostModelSwitch input
|
|
3052
|
+
|
|
3053
|
+
PostModelSwitch hooks receive the same fields as [PreModelSwitch](https://code.claude.com/docs/en/hooks#premodelswitch-input), with `hook_event_name` set to `"PostModelSwitch"` and two more `source` values: `"auto"` for an automatic fallback or other change Claude Code made on its own, and `"resume"` for the model restored when you resume a session.`requested_model` is `null` when `source` is `"auto"`. When `source` is `"resume"`, it is the saved model setting Claude Code restored.
|
|
3054
|
+
|
|
3055
|
+
#### [](https://code.claude.com/docs/en/hooks\#postmodelswitch-decision-control) PostModelSwitch decision control
|
|
3056
|
+
|
|
3057
|
+
Claude Code takes your hook’s [plain-text stdout](https://code.claude.com/docs/en/hooks#exit-code-0) on exit 0, or `additionalContext` from JSON output, and delivers it to Claude with the next request after the switch. In addition to the [JSON output fields](https://code.claude.com/docs/en/hooks#json-output) available to all hooks, you can return:
|
|
3058
|
+
|
|
3059
|
+
| Field | Description |
|
|
3060
|
+
| --- | --- |
|
|
3061
|
+
| `additionalContext` | String added to Claude’s context with the next request. See [Add context for Claude](https://code.claude.com/docs/en/hooks#add-context-for-claude) |
|
|
3062
|
+
|
|
3063
|
+
If the hook hasn’t finished within five seconds after you send the next prompt, Claude Code sends that request without the output and attaches it to the following request instead. If the model changes several times before the next request, Claude Code delivers only the output for the last switch’s target model.
|
|
3064
|
+
|
|
2372
3065
|
### [](https://code.claude.com/docs/en/hooks\#sessionend) SessionEnd
|
|
2373
3066
|
|
|
2374
3067
|
Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
|
|
@@ -2380,8 +3073,8 @@ statistics, or saving session state. Supports matchers to filter by exit reason.
|
|
|
2380
3073
|
| `resume` | Session switched via interactive `/resume` |
|
|
2381
3074
|
| `logout` | User logged out |
|
|
2382
3075
|
| `prompt_input_exit` | User exited while prompt input was visible |
|
|
2383
|
-
| `bypass_permissions_disabled` | Bypass permissions mode was disabled |
|
|
2384
3076
|
| `other` | Other exit reasons |
|
|
3077
|
+
| `bypass_permissions_disabled` | Removed in v2.1.234; Claude Code doesn’t send it. Drop it from your `SessionEnd` matchers |
|
|
2385
3078
|
|
|
2386
3079
|
#### [](https://code.claude.com/docs/en/hooks\#sessionend-input) SessionEnd input
|
|
2387
3080
|
|
|
@@ -2397,15 +3090,22 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
2397
3090
|
}
|
|
2398
3091
|
```
|
|
2399
3092
|
|
|
2400
|
-
SessionEnd hooks have no decision control. They can’t block session termination but can perform cleanup tasks.SessionEnd hooks have a default timeout of 1.5 seconds.
|
|
3093
|
+
SessionEnd hooks have no decision control. They can’t block session termination but can perform cleanup tasks. Claude Code discards their [JSON output fields](https://code.claude.com/docs/en/hooks#json-output), such as `systemMessage`.SessionEnd hooks have a default timeout of 1.5 seconds. It applies when you exit, run `/clear`, or switch sessions with interactive `/resume`. You can give a hook more time in two ways:
|
|
3094
|
+
|
|
3095
|
+
- **Per-hook `timeout`**: set `timeout` in that hook’s configuration. The overall budget rises automatically to match the highest per-hook `timeout` in your settings files, up to 60 seconds. If you raise the budget this way, a hook without its own `timeout` still keeps the default. Timeouts set on plugin-provided hooks don’t raise the budget.
|
|
3096
|
+
- **`CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS`**: set this environment variable in milliseconds to override the budget explicitly. The value you set also becomes the timeout for each hook without its own `timeout`.
|
|
3097
|
+
|
|
3098
|
+
This example sets the budget to 5 seconds:
|
|
2401
3099
|
|
|
2402
3100
|
```
|
|
2403
3101
|
CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude
|
|
2404
3102
|
```
|
|
2405
3103
|
|
|
3104
|
+
Before v2.1.268, `CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS` raised only the overall budget, and a hook without its own `timeout` was still canceled after 1.5 seconds.
|
|
3105
|
+
|
|
2406
3106
|
### [](https://code.claude.com/docs/en/hooks\#elicitation) Elicitation
|
|
2407
3107
|
|
|
2408
|
-
Runs 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.The matcher field matches against the MCP server name.
|
|
3108
|
+
Runs 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.For a complete hook with its settings entry and script, see [Answer a form request from a script](https://code.claude.com/docs/en/hooks#answer-a-form-request-from-a-script).The matcher field matches against the MCP server name.
|
|
2409
3109
|
|
|
2410
3110
|
#### [](https://code.claude.com/docs/en/hooks\#elicitation-input) Elicitation input
|
|
2411
3111
|
|
|
@@ -2416,7 +3116,6 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
2416
3116
|
"session_id": "abc123",
|
|
2417
3117
|
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
|
|
2418
3118
|
"cwd": "/Users/...",
|
|
2419
|
-
"permission_mode": "default",
|
|
2420
3119
|
"hook_event_name": "Elicitation",
|
|
2421
3120
|
"mcp_server_name": "my-mcp-server",
|
|
2422
3121
|
"message": "Please provide your credentials",
|
|
@@ -2437,7 +3136,6 @@ For URL-mode elicitation, used for browser-based authentication:
|
|
|
2437
3136
|
"session_id": "abc123",
|
|
2438
3137
|
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
|
|
2439
3138
|
"cwd": "/Users/...",
|
|
2440
|
-
"permission_mode": "default",
|
|
2441
3139
|
"hook_event_name": "Elicitation",
|
|
2442
3140
|
"mcp_server_name": "my-mcp-server",
|
|
2443
3141
|
"message": "Please authenticate",
|
|
@@ -2448,7 +3146,16 @@ For URL-mode elicitation, used for browser-based authentication:
|
|
|
2448
3146
|
|
|
2449
3147
|
#### [](https://code.claude.com/docs/en/hooks\#elicitation-output) Elicitation output
|
|
2450
3148
|
|
|
2451
|
-
|
|
3149
|
+
An Elicitation hook can answer the request for the user, decline or cancel it, or leave it to the dialog. To answer, decline, or cancel, exit 0 and print a `hookSpecificOutput` object with an `action`. The server gets your answer and no dialog appears. Each row of this table shows what to return for one outcome and what the MCP server receives:
|
|
3150
|
+
|
|
3151
|
+
| To | Return | The server receives |
|
|
3152
|
+
| --- | --- | --- |
|
|
3153
|
+
| Answer for the user | `"action": "accept"`, with the form field values in `content` | `accept` with your `content` |
|
|
3154
|
+
| Decline the request | `"action": "decline"` | `decline` |
|
|
3155
|
+
| Cancel the request | `"action": "cancel"` | `cancel` |
|
|
3156
|
+
| Leave the request to the user | No output, with exit code 0 | The user’s answer from the [dialog](https://code.claude.com/docs/en/mcp#respond-to-mcp-elicitation-requests) |
|
|
3157
|
+
|
|
3158
|
+
This output answers the form-mode request shown under [Elicitation input](https://code.claude.com/docs/en/hooks#elicitation-input). The keys in `content` are the property names from that request’s `requested_schema`:
|
|
2452
3159
|
|
|
2453
3160
|
```
|
|
2454
3161
|
{
|
|
@@ -2462,16 +3169,128 @@ To respond programmatically without showing the dialog, return a JSON object wit
|
|
|
2462
3169
|
}
|
|
2463
3170
|
```
|
|
2464
3171
|
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
3172
|
+
This output declines a request:
|
|
3173
|
+
|
|
3174
|
+
```
|
|
3175
|
+
{
|
|
3176
|
+
"hookSpecificOutput": {
|
|
3177
|
+
"hookEventName": "Elicitation",
|
|
3178
|
+
"action": "decline"
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
```
|
|
3182
|
+
|
|
3183
|
+
In the dialog, selecting **Decline** sends `decline` and pressing `Esc` sends `cancel`, so return the one you want the server to see.For a URL-mode request, a hook that returns `accept` skips the dialog, so the URL never opens.Claude Code discards `reason`, `systemMessage`, and `continue` from an Elicitation hook’s JSON output, whichever `action` you return.
|
|
3184
|
+
|
|
3185
|
+
#### [](https://code.claude.com/docs/en/hooks\#other-ways-to-decline-an-elicitation) Other ways to decline an elicitation
|
|
3186
|
+
|
|
3187
|
+
Your hook can also decline in these ways. The server receives the same `decline` as for `"action": "decline"`:
|
|
2469
3188
|
|
|
2470
|
-
|
|
3189
|
+
- **Exits with code 2**: Claude Code ignores a `hookSpecificOutput` printed by the same hook
|
|
3190
|
+
- **Prints a top-level `"decision": "block"`**: the block overrides an `action` in the same output
|
|
3191
|
+
|
|
3192
|
+
When several hooks match the same request, a decline from one of them overrides an `accept` or `cancel` from another.This script declines URL-mode requests and leaves form requests to the dialog:
|
|
3193
|
+
|
|
3194
|
+
```
|
|
3195
|
+
#!/bin/bash
|
|
3196
|
+
if [ "$(jq -r '.mode')" = "url" ]; then
|
|
3197
|
+
exit 2
|
|
3198
|
+
fi
|
|
3199
|
+
```
|
|
3200
|
+
|
|
3201
|
+
Neither the user nor the server sees why your hook declined, because Claude Code doesn’t show your stderr or your `reason`.Claude Code ignored a top-level `decision` from `Elicitation` and `ElicitationResult` hooks from v2.1.105 until the fix in v2.1.284.
|
|
3202
|
+
|
|
3203
|
+
#### [](https://code.claude.com/docs/en/hooks\#answer-a-form-request-from-a-script) Answer a form request from a script
|
|
3204
|
+
|
|
3205
|
+
This example answers one recurring question for the user. An MCP server named `issue-tracker` asks for a project key in a form, and the hook fills in `DOCS`. The script accepts when `project_key` is the form’s single field. For any other request it prints nothing, so the dialog appears.
|
|
3206
|
+
|
|
3207
|
+
- macOS/Linux
|
|
3208
|
+
|
|
3209
|
+
- Windows (PowerShell)
|
|
3210
|
+
|
|
3211
|
+
|
|
3212
|
+
Register a command hook for the event in your settings file, with the server name as the matcher:
|
|
3213
|
+
|
|
3214
|
+
```
|
|
3215
|
+
{
|
|
3216
|
+
"hooks": {
|
|
3217
|
+
"Elicitation": [\
|
|
3218
|
+
{\
|
|
3219
|
+
"matcher": "issue-tracker",\
|
|
3220
|
+
"hooks": [\
|
|
3221
|
+
{\
|
|
3222
|
+
"type": "command",\
|
|
3223
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/answer-project-key.sh",\
|
|
3224
|
+
"args": []\
|
|
3225
|
+
}\
|
|
3226
|
+
]\
|
|
3227
|
+
}\
|
|
3228
|
+
]
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
```
|
|
3232
|
+
|
|
3233
|
+
Save this script to `.claude/hooks/answer-project-key.sh` in your project and make it executable with `chmod +x`:
|
|
3234
|
+
|
|
3235
|
+
```
|
|
3236
|
+
#!/bin/bash
|
|
3237
|
+
input=$(cat)
|
|
3238
|
+
fields=$(jq -c '.requested_schema.properties // {} | keys' <<<"$input")
|
|
3239
|
+
|
|
3240
|
+
if [ "$fields" = '["project_key"]' ]; then
|
|
3241
|
+
jq -n '{hookSpecificOutput: {hookEventName: "Elicitation", action: "accept", content: {project_key: "DOCS"}}}'
|
|
3242
|
+
fi
|
|
3243
|
+
```
|
|
3244
|
+
|
|
3245
|
+
Register a command hook that runs the script through PowerShell, with the server name as the matcher:
|
|
3246
|
+
|
|
3247
|
+
```
|
|
3248
|
+
{
|
|
3249
|
+
"hooks": {
|
|
3250
|
+
"Elicitation": [\
|
|
3251
|
+
{\
|
|
3252
|
+
"matcher": "issue-tracker",\
|
|
3253
|
+
"hooks": [\
|
|
3254
|
+
{\
|
|
3255
|
+
"type": "command",\
|
|
3256
|
+
"command": "powershell.exe",\
|
|
3257
|
+
"args": [\
|
|
3258
|
+
"-NoProfile",\
|
|
3259
|
+
"-ExecutionPolicy",\
|
|
3260
|
+
"Bypass",\
|
|
3261
|
+
"-File",\
|
|
3262
|
+
"${CLAUDE_PROJECT_DIR}/.claude/hooks/answer-project-key.ps1"\
|
|
3263
|
+
]\
|
|
3264
|
+
}\
|
|
3265
|
+
]\
|
|
3266
|
+
}\
|
|
3267
|
+
]
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
```
|
|
3271
|
+
|
|
3272
|
+
Save this script to `.claude/hooks/answer-project-key.ps1` in your project:
|
|
3273
|
+
|
|
3274
|
+
```
|
|
3275
|
+
$request = [Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
3276
|
+
$fields = @($request.requested_schema.properties.PSObject.Properties.Name)
|
|
3277
|
+
|
|
3278
|
+
if ($fields.Count -eq 1 -and $fields[0] -eq 'project_key') {
|
|
3279
|
+
@{
|
|
3280
|
+
hookSpecificOutput = @{
|
|
3281
|
+
hookEventName = "Elicitation"
|
|
3282
|
+
action = "accept"
|
|
3283
|
+
content = @{ project_key = "DOCS" }
|
|
3284
|
+
}
|
|
3285
|
+
} | ConvertTo-Json -Depth 3
|
|
3286
|
+
}
|
|
3287
|
+
```
|
|
3288
|
+
|
|
3289
|
+
To confirm the hook works, start Claude Code with `claude --debug` and give Claude a task that makes the server ask for the project key. No dialog appears, and the [debug log](https://code.claude.com/docs/en/hooks#debug-hooks) has a line that ends with `Elicitation resolved by hook: {"action":"accept","content":{"project_key":"DOCS"}}`.
|
|
2471
3290
|
|
|
2472
3291
|
### [](https://code.claude.com/docs/en/hooks\#elicitationresult) ElicitationResult
|
|
2473
3292
|
|
|
2474
|
-
Runs 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.The matcher field matches against the MCP server name.
|
|
3293
|
+
Runs 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.When an [Elicitation](https://code.claude.com/docs/en/hooks#elicitation) hook answers a request, Claude Code sends that answer to the server without running ElicitationResult hooks.The matcher field matches against the MCP server name.
|
|
2475
3294
|
|
|
2476
3295
|
#### [](https://code.claude.com/docs/en/hooks\#elicitationresult-input) ElicitationResult input
|
|
2477
3296
|
|
|
@@ -2482,43 +3301,68 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
2482
3301
|
"session_id": "abc123",
|
|
2483
3302
|
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
|
|
2484
3303
|
"cwd": "/Users/...",
|
|
2485
|
-
"permission_mode": "default",
|
|
2486
3304
|
"hook_event_name": "ElicitationResult",
|
|
2487
3305
|
"mcp_server_name": "my-mcp-server",
|
|
2488
3306
|
"action": "accept",
|
|
2489
3307
|
"content": { "username": "alice" },
|
|
2490
|
-
"mode": "form"
|
|
2491
|
-
"elicitation_id": "elicit-123"
|
|
3308
|
+
"mode": "form"
|
|
2492
3309
|
}
|
|
2493
3310
|
```
|
|
2494
3311
|
|
|
2495
3312
|
#### [](https://code.claude.com/docs/en/hooks\#elicitationresult-output) ElicitationResult output
|
|
2496
3313
|
|
|
2497
|
-
|
|
3314
|
+
An ElicitationResult hook can let the user’s response through, change its values, or block it. To change or block the response, exit 0 and print a `hookSpecificOutput` object with an `action`. Each row of this table shows what to return for one outcome and what the MCP server receives:
|
|
3315
|
+
|
|
3316
|
+
| To | Return | The server receives |
|
|
3317
|
+
| --- | --- | --- |
|
|
3318
|
+
| Let the response through | No output, with exit code 0 | The user’s response, unchanged |
|
|
3319
|
+
| Change the submitted values | `"action": "accept"`, with the new values in `content` | `accept` with your `content` in place of the user’s values |
|
|
3320
|
+
| Block the response | `"action": "decline"` | `decline`, without the user’s values |
|
|
3321
|
+
| Cancel the request | `"action": "cancel"` | `cancel`, along with the values the user submitted. To withhold them, return `"decline"` |
|
|
3322
|
+
|
|
3323
|
+
This output changes the response shown under [ElicitationResult input](https://code.claude.com/docs/en/hooks#elicitationresult-input), so the server receives `alice@example.com` where the user submitted `alice`:
|
|
2498
3324
|
|
|
2499
3325
|
```
|
|
2500
3326
|
{
|
|
2501
3327
|
"hookSpecificOutput": {
|
|
2502
3328
|
"hookEventName": "ElicitationResult",
|
|
2503
|
-
"action": "
|
|
2504
|
-
"content": {
|
|
3329
|
+
"action": "accept",
|
|
3330
|
+
"content": {
|
|
3331
|
+
"username": "alice@example.com"
|
|
3332
|
+
}
|
|
2505
3333
|
}
|
|
2506
3334
|
}
|
|
2507
3335
|
```
|
|
2508
3336
|
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
3337
|
+
Your `content` replaces the user’s whole `content` object, so include the fields you aren’t changing. Return `action` along with it, because Claude Code ignores a `hookSpecificOutput` that has no `action`.ElicitationResult hooks also run when the user declines or cancels, and your `action` replaces theirs. Check that the input’s `action` is `accept` before you return `accept`, or your hook turns a declined request into an accepted one. This script makes the same change when the user accepted, keeps the other fields, and prints nothing otherwise:
|
|
3338
|
+
|
|
3339
|
+
```
|
|
3340
|
+
#!/bin/bash
|
|
3341
|
+
input=$(cat)
|
|
2513
3342
|
|
|
2514
|
-
|
|
3343
|
+
if [ "$(jq -r '.action' <<<"$input")" = "accept" ]; then
|
|
3344
|
+
jq '{hookSpecificOutput: {hookEventName: "ElicitationResult", action: "accept", content: (.content + {username: (.content.username + "@example.com")})}}' <<<"$input"
|
|
3345
|
+
fi
|
|
3346
|
+
```
|
|
3347
|
+
|
|
3348
|
+
This output blocks the response:
|
|
3349
|
+
|
|
3350
|
+
```
|
|
3351
|
+
{
|
|
3352
|
+
"hookSpecificOutput": {
|
|
3353
|
+
"hookEventName": "ElicitationResult",
|
|
3354
|
+
"action": "decline"
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
```
|
|
3358
|
+
|
|
3359
|
+
Exit code 2 and a top-level `"decision": "block"` also block the response. [Other ways to decline an elicitation](https://code.claude.com/docs/en/hooks#other-ways-to-decline-an-elicitation) covers which one takes effect when a hook combines them, what the user sees, and which versions ignored `decision`.Claude Code discards `reason`, `systemMessage`, and `continue` from an ElicitationResult hook’s JSON output, whichever `action` you return.
|
|
2515
3360
|
|
|
2516
3361
|
## [](https://code.claude.com/docs/en/hooks\#prompt-based-hooks) Prompt-based hooks
|
|
2517
3362
|
|
|
2518
3363
|
In addition to command, HTTP, and MCP tool hooks, Claude Code supports prompt-based hooks (`type: "prompt"`) that use an LLM to evaluate whether to allow or block an action, and agent hooks (`type: "agent"`) that spawn an agentic verifier with tool access. Not all events support every hook type.Events that support all five hook types (`command`, `http`, `mcp_tool`, `prompt`, and `agent`):
|
|
2519
3364
|
|
|
2520
3365
|
- `PermissionDenied`
|
|
2521
|
-
- `PermissionRequest`
|
|
2522
3366
|
- `PostToolBatch`
|
|
2523
3367
|
- `PostToolUse`
|
|
2524
3368
|
- `PostToolUseFailure`
|
|
@@ -2531,36 +3375,40 @@ In addition to command, HTTP, and MCP tool hooks, Claude Code supports prompt-ba
|
|
|
2531
3375
|
- `UserPromptExpansion`
|
|
2532
3376
|
- `UserPromptSubmit`
|
|
2533
3377
|
|
|
2534
|
-
Events that support `command`, `http`, and `mcp_tool` hooks but not `prompt` or `agent`:
|
|
3378
|
+
`PermissionRequest` supports `command`, `http`, `mcp_tool`, and `prompt` hooks but not `agent` hooks. If you configure an agent hook on this event, Claude Code skips it and the permission flow proceeds unchanged. To allow or deny from a hook, return the [decision object](https://code.claude.com/docs/en/hooks#permissionrequest-decision-control) from a command or HTTP hook.Events that support `command`, `http`, and `mcp_tool` hooks but not `prompt` or `agent`:
|
|
2535
3379
|
|
|
2536
3380
|
- `ConfigChange`
|
|
2537
3381
|
- `CwdChanged`
|
|
3382
|
+
- `DirectoryAdded`
|
|
2538
3383
|
- `Elicitation`
|
|
2539
3384
|
- `ElicitationResult`
|
|
2540
3385
|
- `FileChanged`
|
|
2541
3386
|
- `InstructionsLoaded`
|
|
3387
|
+
- `MessageDisplay`
|
|
2542
3388
|
- `Notification`
|
|
2543
3389
|
- `PostCompact`
|
|
3390
|
+
- `PostModelSwitch`
|
|
2544
3391
|
- `PreCompact`
|
|
3392
|
+
- `PreModelSwitch`
|
|
2545
3393
|
- `SessionEnd`
|
|
2546
3394
|
- `StopFailure`
|
|
2547
3395
|
- `SubagentStart`
|
|
2548
3396
|
- `WorktreeCreate`
|
|
2549
3397
|
- `WorktreeRemove`
|
|
2550
3398
|
|
|
2551
|
-
`SessionStart` and `Setup` support `command` and `mcp_tool` hooks. They don’t support `http`, `prompt`, or `agent` hooks.
|
|
3399
|
+
`SessionStart` and `Setup` support `command` and `mcp_tool` hooks, and [MCP tool hook fields](https://code.claude.com/docs/en/hooks#mcp-tool-hook-fields) describes when their `mcp_tool` hooks run. They don’t support `http`, `prompt`, or `agent` hooks.
|
|
2552
3400
|
|
|
2553
3401
|
### [](https://code.claude.com/docs/en/hooks\#how-prompt-based-hooks-work) How prompt-based hooks work
|
|
2554
3402
|
|
|
2555
3403
|
Instead of executing a Bash command, prompt-based hooks:
|
|
2556
3404
|
|
|
2557
|
-
1. Send the hook input and your prompt to a Claude model,
|
|
3405
|
+
1. Send the hook input and your prompt to a Claude model, by default the one Claude Code uses for [background functionality](https://code.claude.com/docs/en/costs#background-token-usage)
|
|
2558
3406
|
2. The LLM responds with structured JSON containing a decision
|
|
2559
3407
|
3. Claude Code processes the decision automatically
|
|
2560
3408
|
|
|
2561
3409
|
### [](https://code.claude.com/docs/en/hooks\#prompt-hook-configuration) Prompt hook configuration
|
|
2562
3410
|
|
|
2563
|
-
Set `type` to `"prompt"` and provide a `prompt` string instead of a `command`. Use the `$ARGUMENTS` placeholder to inject the hook’s JSON input data into your prompt text.
|
|
3411
|
+
Set `type` to `"prompt"` and provide a `prompt` string instead of a `command`. Use the `$ARGUMENTS` placeholder to inject the hook’s JSON input data into your prompt text.In a prompt or [agent hook](https://code.claude.com/docs/en/hooks#agent-based-hooks), you can write the `prompt` as a rule about what to block or allow, such as “Block any Bash command that reads `.env` files”, or as a condition that must hold, such as “All unit tests pass”.This `Stop` hook asks the LLM to evaluate whether all tasks are complete before allowing Claude to finish:
|
|
2564
3412
|
|
|
2565
3413
|
```
|
|
2566
3414
|
{
|
|
@@ -2583,9 +3431,9 @@ Set `type` to `"prompt"` and provide a `prompt` string instead of a `command`. U
|
|
|
2583
3431
|
| --- | --- | --- |
|
|
2584
3432
|
| `type` | yes | Must be `"prompt"` |
|
|
2585
3433
|
| `prompt` | yes | The prompt text to send to the LLM. Use `$ARGUMENTS` as a placeholder for the hook input JSON. If `$ARGUMENTS` is not present, input JSON is appended to the prompt |
|
|
2586
|
-
| `model` | no | Model to use for evaluation. Defaults to
|
|
3434
|
+
| `model` | no | Model to use for evaluation. Defaults to the model Claude Code uses for [background functionality](https://code.claude.com/docs/en/costs#background-token-usage) |
|
|
2587
3435
|
| `timeout` | no | Timeout in seconds. Default: 30 |
|
|
2588
|
-
| `continueOnBlock` | no |
|
|
3436
|
+
| `continueOnBlock` | no | On the events it applies to, `true` feeds an `ok: false` reason back to Claude and continues instead of ending the turn. Default: `false`. See [Response schema](https://code.claude.com/docs/en/hooks#response-schema) for per-event behavior |
|
|
2589
3437
|
|
|
2590
3438
|
### [](https://code.claude.com/docs/en/hooks\#response-schema) Response schema
|
|
2591
3439
|
|
|
@@ -2594,22 +3442,25 @@ The LLM must respond with JSON containing:
|
|
|
2594
3442
|
```
|
|
2595
3443
|
{
|
|
2596
3444
|
"ok": true | false,
|
|
2597
|
-
"reason": "Explanation for the decision"
|
|
3445
|
+
"reason": "Explanation for the decision",
|
|
3446
|
+
"impossible": true | false
|
|
2598
3447
|
}
|
|
2599
3448
|
```
|
|
2600
3449
|
|
|
2601
3450
|
| Field | Description |
|
|
2602
3451
|
| --- | --- |
|
|
2603
|
-
| `ok` | `true` to allow. `false
|
|
2604
|
-
| `reason` | Required when `ok` is `false
|
|
3452
|
+
| `ok` | `true` to allow. For `false`, see the per-event behavior below |
|
|
3453
|
+
| `reason` | Required when `ok` is `false` |
|
|
3454
|
+
| `impossible` | Optional. The model returns it with `ok: false` when it judges the condition can never be satisfied. On `Stop` and `SubagentStop`, Claude Code then lets the turn end instead of feeding the reason back. Agent hooks and other events ignore it |
|
|
2605
3455
|
|
|
2606
3456
|
What happens on `ok: false` depends on the event:
|
|
2607
3457
|
|
|
2608
|
-
- `Stop` and `SubagentStop`: the reason is fed back to Claude as its next instruction and the turn continues
|
|
2609
|
-
- `PreToolUse`: the tool call is denied and the reason
|
|
3458
|
+
- `Stop` and `SubagentStop`: the reason is fed back to Claude as its next instruction and the turn continues, unless the response also sets `impossible: true`, in which case Claude Code allows the stop and the turn ends
|
|
3459
|
+
- `PreToolUse`: the tool call is denied; by default the turn ends and the deny reason appears in the chat as a warning line. Set `continueOnBlock: true` to instead return the reason to Claude as the tool error so it can adjust and continue, equivalent to a command hook’s `permissionDecision: "deny"`. Before v2.1.210, the deny reason was returned to Claude as the tool error and the turn continued
|
|
2610
3460
|
- `PostToolUse`: by default the turn ends and the reason appears in the chat as a warning line. Set `continueOnBlock: true` to feed the reason back to Claude and continue the turn instead
|
|
2611
3461
|
- `PostToolBatch`, `UserPromptSubmit`, and `UserPromptExpansion`: the turn ends and the reason appears as a warning line. These events end the turn on `decision: "block"` regardless of `continue`
|
|
2612
|
-
- `PostToolUseFailure
|
|
3462
|
+
- `PostToolUseFailure` and `TaskCreated`: the reason is returned to Claude as a tool error and the turn continues, regardless of `continueOnBlock`
|
|
3463
|
+
- `TaskCompleted`: when it fires because a task is marked completed during a turn, the reason is returned to Claude as a tool error and the turn continues, regardless of `continueOnBlock`. When it fires because a teammate stops, it behaves like `TeammateIdle` and halts the teammate by default
|
|
2613
3464
|
- `TeammateIdle`: by default the teammate stops and the reason appears as a warning line. Set `continueOnBlock: true` to feed the reason back to the teammate and keep it working instead
|
|
2614
3465
|
- `PermissionRequest`: `ok: false` has no effect. To deny an approval from a hook, use a [command hook](https://code.claude.com/docs/en/hooks#command-hook-fields) returning `hookSpecificOutput.decision.behavior: "deny"`
|
|
2615
3466
|
- `PermissionDenied`: `ok: false` has no effect because the denial already happened. The only output this event reads is `hookSpecificOutput.retry`, which prompt and agent hooks can’t set. They run on this event, but their output is discarded. Use a [command hook](https://code.claude.com/docs/en/hooks#command-hook-fields) to return `retry`
|
|
@@ -2618,7 +3469,7 @@ If you need finer control on any event, use a [command hook](https://code.claude
|
|
|
2618
3469
|
|
|
2619
3470
|
### [](https://code.claude.com/docs/en/hooks\#check-multiple-conditions-before-stopping) Check multiple conditions before stopping
|
|
2620
3471
|
|
|
2621
|
-
This `Stop` hook uses a detailed prompt to check three conditions before allowing Claude to stop. `SubagentStop` hooks use the same format to evaluate whether a [subagent](https://code.claude.com/docs/en/sub-agents) should stop. If `"ok"`
|
|
3472
|
+
This `Stop` hook uses a detailed prompt to check three conditions before allowing Claude to stop. `SubagentStop` hooks use the same format to evaluate whether a [subagent](https://code.claude.com/docs/en/sub-agents) should stop. If the model returns `"ok": false` because the condition isn’t met yet, Claude continues working with the provided reason as its next instruction:
|
|
2622
3473
|
|
|
2623
3474
|
```
|
|
2624
3475
|
{
|
|
@@ -2642,7 +3493,7 @@ This `Stop` hook uses a detailed prompt to check three conditions before allowin
|
|
|
2642
3493
|
|
|
2643
3494
|
Agent hooks are experimental. Behavior and configuration may change in future releases. For production workflows, prefer [command hooks](https://code.claude.com/docs/en/hooks#command-hook-fields).
|
|
2644
3495
|
|
|
2645
|
-
Agent-based hooks (`type: "agent"`) are like prompt-based hooks but with multi-turn tool access. Instead of a single LLM call, an agent hook spawns a subagent that can read files, search code, and inspect the codebase to verify conditions. Agent hooks support the same events as prompt-based hooks.
|
|
3496
|
+
Agent-based hooks (`type: "agent"`) are like prompt-based hooks but with multi-turn tool access. Instead of a single LLM call, an agent hook spawns a subagent that can read files, search code, and inspect the codebase to verify conditions. Agent hooks support the same events as [prompt-based hooks](https://code.claude.com/docs/en/hooks#prompt-based-hooks), except `PermissionRequest`.
|
|
2646
3497
|
|
|
2647
3498
|
### [](https://code.claude.com/docs/en/hooks\#how-agent-hooks-work) How agent hooks work
|
|
2648
3499
|
|
|
@@ -2651,22 +3502,13 @@ When an agent hook fires:
|
|
|
2651
3502
|
1. Claude Code spawns a subagent with your prompt and the hook’s JSON input
|
|
2652
3503
|
2. The subagent can use tools like Read, Grep, and Glob to investigate
|
|
2653
3504
|
3. After up to 50 turns, the subagent returns a structured `{ "ok": true/false }` decision
|
|
2654
|
-
4. Claude Code
|
|
3505
|
+
4. Claude Code allows the action if `ok` is `true`. If `ok` is `false`, Claude Code handles the block the same way as a prompt hook with `continueOnBlock: true` on that event, as listed under [Response schema](https://code.claude.com/docs/en/hooks#response-schema)
|
|
2655
3506
|
|
|
2656
3507
|
Agent hooks are useful when verification requires inspecting actual files or test output, not just evaluating the hook input data alone.
|
|
2657
3508
|
|
|
2658
3509
|
### [](https://code.claude.com/docs/en/hooks\#agent-hook-configuration) Agent hook configuration
|
|
2659
3510
|
|
|
2660
|
-
Set `type` to `"agent"` and provide a `prompt` string. The configuration fields are the same as [prompt hooks](https://code.claude.com/docs/en/hooks#prompt-hook-configuration),
|
|
2661
|
-
|
|
2662
|
-
| Field | Required | Description |
|
|
2663
|
-
| --- | --- | --- |
|
|
2664
|
-
| `type` | yes | Must be `"agent"` |
|
|
2665
|
-
| `prompt` | yes | Prompt describing what to verify. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
|
|
2666
|
-
| `model` | no | Model to use. Defaults to a fast model |
|
|
2667
|
-
| `timeout` | no | Timeout in seconds. Default: 60 |
|
|
2668
|
-
|
|
2669
|
-
The response schema is the same as prompt hooks: `{ "ok": true }` to allow or `{ "ok": false, "reason": "..." }` to block.This `Stop` hook verifies that all unit tests pass before allowing Claude to finish:
|
|
3511
|
+
Set `type` to `"agent"` and provide a `prompt` string, using `$ARGUMENTS` as a placeholder for the hook input JSON. The configuration fields are the same as [prompt hooks](https://code.claude.com/docs/en/hooks#prompt-hook-configuration), except that agent hooks have a longer default timeout of 60 seconds and no `continueOnBlock` field.The response schema is `{ "ok": true }` to allow or `{ "ok": false, "reason": "..." }` to block. On `ok: false`, Claude Code handles an agent hook the way it handles a [prompt hook with `continueOnBlock: true`](https://code.claude.com/docs/en/hooks#response-schema) on the same event; agent hooks have no `continueOnBlock` field, and don’t support the prompt-hook `impossible` field.This `Stop` hook verifies that all unit tests pass before allowing Claude to finish:
|
|
2670
3512
|
|
|
2671
3513
|
```
|
|
2672
3514
|
{
|
|
@@ -2692,7 +3534,7 @@ By default, hooks block Claude’s execution until they complete. For long-runni
|
|
|
2692
3534
|
|
|
2693
3535
|
### [](https://code.claude.com/docs/en/hooks\#configure-an-async-hook) Configure an async hook
|
|
2694
3536
|
|
|
2695
|
-
Add `"async": true` to a command hook’s configuration to run it in the background without blocking Claude. This field is only available on `type: "command"` hooks.This hook runs a test script after every `Write` tool call. Claude continues working immediately while `run-tests.sh` executes
|
|
3537
|
+
Add `"async": true` to a command hook’s configuration to run it in the background without blocking Claude. This field is only available on `type: "command"` hooks.This hook runs a test script after every `Write` tool call. Claude continues working immediately while `run-tests.sh` executes. When the script finishes, its output is delivered on the next conversation turn:
|
|
2696
3538
|
|
|
2697
3539
|
```
|
|
2698
3540
|
{
|
|
@@ -2704,8 +3546,7 @@ Add `"async": true` to a command hook’s configuration to run it in the backgro
|
|
|
2704
3546
|
{\
|
|
2705
3547
|
"type": "command",\
|
|
2706
3548
|
"command": "/path/to/run-tests.sh",\
|
|
2707
|
-
"async": true
|
|
2708
|
-
"timeout": 120\
|
|
3549
|
+
"async": true\
|
|
2709
3550
|
}\
|
|
2710
3551
|
]\
|
|
2711
3552
|
}\
|
|
@@ -2714,11 +3555,19 @@ Add `"async": true` to a command hook’s configuration to run it in the backgro
|
|
|
2714
3555
|
}
|
|
2715
3556
|
```
|
|
2716
3557
|
|
|
2717
|
-
|
|
3558
|
+
Once an async hook is running in the background, Claude Code doesn’t enforce `timeout` on it. Claude Code still enforces `timeout` on a hook you run with `asyncRewake`.Claude Code delivers an async hook’s results only while the session runs:
|
|
3559
|
+
|
|
3560
|
+
- In [non-interactive mode](https://code.claude.com/docs/en/headless) with the `-p` flag, Claude Code kills any async hook still running at teardown and finalizes it with outcome `cancelled`
|
|
3561
|
+
- If your hook’s work must outlive a `claude -p` session, start a fully detached process from it
|
|
2718
3562
|
|
|
2719
3563
|
### [](https://code.claude.com/docs/en/hooks\#how-async-hooks-execute) How async hooks execute
|
|
2720
3564
|
|
|
2721
|
-
When an async hook fires, Claude Code starts the hook process and immediately continues without waiting for it to finish. The hook receives the same JSON input via stdin as a synchronous hook.After the background process exits,
|
|
3565
|
+
When an async hook fires, Claude Code starts the hook process and immediately continues without waiting for it to finish. The hook receives the same JSON input via stdin as a synchronous hook.After the background process exits, Claude Code delivers the `additionalContext` and `systemMessage` fields from the hook’s JSON response to Claude on the next conversation turn. Unlike a synchronous hook’s `systemMessage`, neither field is shown to you.Print the JSON response alone on stdout or on one line of its own:
|
|
3566
|
+
|
|
3567
|
+
- **Alone on stdout**: when the response is the only text on stdout, it can span several lines, such as pretty-printed `jq` output. Spanning several lines requires Claude Code v2.1.295 or later.
|
|
3568
|
+
- **On one line of its own**: an async hook can print other text to stdout when the response fits on one line by itself, for example with `jq -c`.
|
|
3569
|
+
|
|
3570
|
+
Claude Code validates that JSON response against the same [output schema](https://code.claude.com/docs/en/hooks#json-output) as synchronous hooks, and drops any field whose value has the wrong type, such as a `systemMessage` that isn’t a string, instead of delivering it. Run with `--debug` to see a warning naming each dropped field. Before v2.1.202, malformed JSON output from an async hook could crash the session, and the crash recurred each time the session was resumed.Async hook completion notifications are suppressed by default. To see them, enable verbose mode with `Ctrl+O` or start Claude Code with `--verbose`.
|
|
2722
3571
|
|
|
2723
3572
|
### [](https://code.claude.com/docs/en/hooks\#run-tests-after-file-changes) Run tests after file changes
|
|
2724
3573
|
|
|
@@ -2762,8 +3611,7 @@ Then add this configuration to `.claude/settings.json` in your project root. The
|
|
|
2762
3611
|
"type": "command",\
|
|
2763
3612
|
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests-async.sh",\
|
|
2764
3613
|
"args": [],\
|
|
2765
|
-
"async": true
|
|
2766
|
-
"timeout": 300\
|
|
3614
|
+
"async": true\
|
|
2767
3615
|
}\
|
|
2768
3616
|
]\
|
|
2769
3617
|
}\
|
|
@@ -2774,21 +3622,26 @@ Then add this configuration to `.claude/settings.json` in your project root. The
|
|
|
2774
3622
|
|
|
2775
3623
|
### [](https://code.claude.com/docs/en/hooks\#limitations) Limitations
|
|
2776
3624
|
|
|
2777
|
-
Async hooks have
|
|
3625
|
+
Async hooks have additional constraints compared to synchronous hooks:
|
|
2778
3626
|
|
|
2779
|
-
- Only `type: "command"` hooks support `async`. Prompt-based hooks can’t run asynchronously.
|
|
2780
|
-
- Async hooks can’t block tool calls or return decisions. By the time the hook completes, the triggering action has already proceeded.
|
|
2781
3627
|
- Hook output is delivered on the next conversation turn. If the session is idle, the response waits until the next user interaction. Exception: an `asyncRewake` hook that exits with code 2 wakes Claude immediately even when the session is idle.
|
|
2782
|
-
- Each execution creates a separate background process.
|
|
3628
|
+
- Each execution creates a separate background process.
|
|
2783
3629
|
|
|
2784
3630
|
## [](https://code.claude.com/docs/en/hooks\#security-considerations) Security considerations
|
|
2785
3631
|
|
|
2786
3632
|
### [](https://code.claude.com/docs/en/hooks\#disclaimer) Disclaimer
|
|
2787
3633
|
|
|
2788
|
-
Command hooks run with your system user’s full permissions.
|
|
2789
|
-
|
|
2790
3634
|
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.
|
|
2791
3635
|
|
|
3636
|
+
### [](https://code.claude.com/docs/en/hooks\#workspace-trust) Workspace trust
|
|
3637
|
+
|
|
3638
|
+
Claude Code checks workspace trust before it runs any hook from a settings file. What counts as trusted depends on the session type:
|
|
3639
|
+
|
|
3640
|
+
- **Interactive session**: Claude Code holds back hooks from every settings file, including your own `~/.claude/settings.json`, until you accept the [workspace trust dialog](https://code.claude.com/docs/en/permissions#project-allow-rules-and-workspace-trust) for the folder, or for a parent directory whose trust extends to it
|
|
3641
|
+
- **`-p` or SDK session**: Claude Code never shows the dialog and treats the folder as trusted, so hooks committed in a repository’s `.claude/settings.json` run in a folder you’ve never trusted
|
|
3642
|
+
|
|
3643
|
+
Before you script `claude -p` over a repository you didn’t write, review its `.claude/` settings files, start with [`--bare`](https://code.claude.com/docs/en/headless#start-faster-with-bare-mode), or [turn hooks off for that run](https://code.claude.com/docs/en/hooks#disable-or-remove-hooks) with `--settings '{"disableAllHooks": true}'`. Frontmatter hooks in a project subagent follow a stricter rule than settings-file hooks. [What runs before you trust a folder](https://code.claude.com/docs/en/permissions#what-runs-before-you-trust-a-folder) lists each kind of repository content by session type.
|
|
3644
|
+
|
|
2792
3645
|
### [](https://code.claude.com/docs/en/hooks\#security-best-practices) Security best practices
|
|
2793
3646
|
|
|
2794
3647
|
Keep these practices in mind when writing hooks:
|
|
@@ -2801,7 +3654,7 @@ Keep these practices in mind when writing hooks:
|
|
|
2801
3654
|
|
|
2802
3655
|
## [](https://code.claude.com/docs/en/hooks\#windows-powershell-tool) Windows PowerShell tool
|
|
2803
3656
|
|
|
2804
|
-
On Windows, you can run individual hooks in PowerShell by setting `"shell": "powershell"` on a command hook.
|
|
3657
|
+
On Windows, you can run individual hooks in PowerShell by setting `"shell": "powershell"` on a command hook. Claude Code auto-detects `pwsh.exe`, the PowerShell 7 and later executable, and falls back to `powershell.exe` for Windows PowerShell 5.1.
|
|
2805
3658
|
|
|
2806
3659
|
```
|
|
2807
3660
|
{
|
|
@@ -2822,7 +3675,7 @@ On Windows, you can run individual hooks in PowerShell by setting `"shell": "pow
|
|
|
2822
3675
|
}
|
|
2823
3676
|
```
|
|
2824
3677
|
|
|
2825
|
-
To reference the project root from a PowerShell shell-form command, write `${CLAUDE_PROJECT_DIR}` or `$env:CLAUDE_PROJECT_DIR`.
|
|
3678
|
+
To reference the project root from a PowerShell shell-form command, write `${CLAUDE_PROJECT_DIR}` or `$env:CLAUDE_PROJECT_DIR`. Claude Code rewrites the `${CLAUDE_PROJECT_DIR}`, `${CLAUDE_PLUGIN_ROOT}`, and `${CLAUDE_PLUGIN_DATA}` placeholders in a PowerShell shell-form command to PowerShell’s `${env:NAME}` form, whether the hook is defined in `settings.json`, a plugin, or a skill. PowerShell then resolves the value from the exported environment after parsing, so the placeholder works inside double-quoted strings but not inside single-quoted strings, where PowerShell never expands variables.Don’t write the bare `$CLAUDE_PROJECT_DIR` spelling in a PowerShell hook. PowerShell parses it as an undefined local variable and resolves it to `$null`, which leaves the script path without its project-root prefix. Claude Code doesn’t rewrite that form; it logs a warning in the [debug log](https://code.claude.com/docs/en/hooks#debug-hooks) instead.The example below shows a `settings.json` hook that runs a project script with the `$env:` form:
|
|
2826
3679
|
|
|
2827
3680
|
```
|
|
2828
3681
|
{
|
|
@@ -2834,29 +3687,19 @@ To reference the project root from a PowerShell shell-form command, write `${CLA
|
|
|
2834
3687
|
|
|
2835
3688
|
## [](https://code.claude.com/docs/en/hooks\#debug-hooks) Debug hooks
|
|
2836
3689
|
|
|
2837
|
-
Hook execution details
|
|
3690
|
+
Hook execution details are written to the debug log file. Start Claude Code with `claude --debug-file <path>` to write the log to a known location, or run `claude --debug` and read the log at `~/.claude/debug/<session-id>.txt`. The `--debug` flag doesn’t print to the terminal.For example, a `PostToolUse` hook on `Write` whose command prints `hook-ran` produces entries like:
|
|
2838
3691
|
|
|
2839
3692
|
```
|
|
2840
|
-
[DEBUG]
|
|
2841
|
-
[DEBUG]
|
|
2842
|
-
[DEBUG] Executing hook command: <Your command> with timeout 600000ms
|
|
2843
|
-
[DEBUG] Hook command completed with status 0: <Your stdout>
|
|
3693
|
+
2026-07-19T02:03:24.382Z [DEBUG] Hook output does not start with {, treating as plain text
|
|
3694
|
+
2026-07-19T02:03:24.382Z [DEBUG] "Hook PostToolUse:Write (PostToolUse) success:\nhook-ran"
|
|
2844
3695
|
```
|
|
2845
3696
|
|
|
2846
|
-
For more granular hook matching details, set `CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose` to see additional log lines such as hook matcher counts and query matching.For troubleshooting common issues like hooks not firing, Stop hooks that keep blocking, or configuration errors, see [Limitations and troubleshooting](https://code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting) in the guide. For a broader diagnostic walkthrough covering `/context`, `/doctor`, and settings precedence, see [Debug your config](https://code.claude.com/docs/en/debug-your-config).
|
|
3697
|
+
To find a slow hook, search the log for `Hooks:` lines that end in a duration. In Claude Code v2.1.296 or later, each command hook on a tool event, `UserPromptSubmit`, `SessionStart`, `Stop`, and several other events leaves one when it finishes, whatever it printed. The line gives the event name, joined by a colon to the tool name or other value the hook matched on, then the hook’s command in brackets, the plugin it came from if any, how the run ended, and how long it took, as in `Hooks: PostToolUse:Write [.claude/hooks/log-write.sh] finished with status 0 (31ms)`. A run can also end as `timed out after <N>ms`, `cancelled`, `moved to the background`, or `failed to start`. On some events, such as `Notification`, `SessionEnd`, and `PreCompact`, a command hook leaves a `completed with status` line without a duration instead.For more granular hook matching details, set `CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose` to see additional log lines such as hook matcher counts and query matching.For troubleshooting common issues like hooks not firing, Stop hooks that keep blocking, or configuration errors, see [Limitations and troubleshooting](https://code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting) in the guide. For a broader diagnostic walkthrough covering `/context`, `/doctor`, and settings precedence, see [Debug your config](https://code.claude.com/docs/en/debug-your-config).
|
|
2847
3698
|
|
|
2848
3699
|
Was this page helpful?
|
|
2849
3700
|
|
|
2850
3701
|
YesNo
|
|
2851
3702
|
|
|
2852
|
-
[Checkpointing](https://code.claude.com/docs/en/checkpointing) [Plugins reference](https://code.claude.com/docs/en/plugins-reference)
|
|
2853
|
-
|
|
2854
|
-
Ctrl+I
|
|
2855
|
-
|
|
2856
3703
|
Assistant
|
|
2857
3704
|
|
|
2858
|
-
Responses are generated using AI and may contain mistakes.
|
|
2859
|
-
|
|
2860
|
-

|
|
2861
|
-
|
|
2862
|
-

|
|
3705
|
+
Responses are generated using AI and may contain mistakes.
|