claude_hooks 1.2.1 โ 1.3.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 +6 -0
- data/README.md +3 -1
- data/docs/API/DIRECTORY_ADDED.md +34 -0
- data/docs/external/claude-hooks-reference.md +271 -107
- data/lib/claude_hooks/directory_added.rb +24 -0
- data/lib/claude_hooks/output/base.rb +2 -0
- data/lib/claude_hooks/output/directory_added.rb +21 -0
- data/lib/claude_hooks/version.rb +1 -1
- data/lib/claude_hooks.rb +2 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ee18a384c04bcfff4fa734935277a938e14d2799646369e8db8e013c5a7b33ce
|
|
4
|
+
data.tar.gz: b784bc4cc4e52096b563c25a2c6d19175a38ac37e6ea72d110c32c3887566128
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 25345ee635cc383661393b66bca8d82f476615e311fb07d3206630b4cc13b9972c62cb08efe2d0ba03ab053ce40f07bdac2dfa3b0d633377150474ba37743e5d
|
|
7
|
+
data.tar.gz: ed9511e85a7a74e9ea851e5ece032afcb6c860f48b52143da80fc999a81dce35374ff1df3fca1914d2c91963578c7f7561d94890bb6f39bf23e530599b2a107e
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.0] - 2026-08-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`DirectoryAdded` hook event** (`ClaudeHooks::DirectoryAdded` / `ClaudeHooks::Output::DirectoryAdded`): non-blocking hook that runs after a working directory is added mid-session via `/add-dir` or the SDK `register_repo_root` control request. Exposes `directory` and `source` readers plus a `system_message!` builder.
|
|
13
|
+
|
|
8
14
|
## [1.2.1] - 2026-07-15
|
|
9
15
|
|
|
10
16
|
### Added
|
data/README.md
CHANGED
|
@@ -289,6 +289,7 @@ The framework supports the following hook types:
|
|
|
289
289
|
| **[PostCompact](docs/API/POST_COMPACT.md)** | `ClaudeHooks::PostCompact` | Runs after transcript compaction completes |
|
|
290
290
|
| **[ConfigChange](docs/API/CONFIG_CHANGE.md)** | `ClaudeHooks::ConfigChange` | Runs when Claude Code configuration changes; can block it |
|
|
291
291
|
| **[CwdChanged](docs/API/CWD_CHANGED.md)** | `ClaudeHooks::CwdChanged` | Runs when the working directory changes |
|
|
292
|
+
| **[DirectoryAdded](docs/API/DIRECTORY_ADDED.md)** | `ClaudeHooks::DirectoryAdded` | Runs when a working directory is added mid-session |
|
|
292
293
|
| **[FileChanged](docs/API/FILE_CHANGED.md)** | `ClaudeHooks::FileChanged` | Runs when a watched file is created, modified, or deleted |
|
|
293
294
|
| **[InstructionsLoaded](docs/API/INSTRUCTIONS_LOADED.md)** | `ClaudeHooks::InstructionsLoaded` | Runs when a CLAUDE.md instructions file is loaded |
|
|
294
295
|
| **[Elicitation](docs/API/ELICITATION.md)** | `ClaudeHooks::Elicitation` | Runs when an MCP server requests user input |
|
|
@@ -417,6 +418,7 @@ The framework supports all existing hook types with their respective input field
|
|
|
417
418
|
| **PostCompact** | `trigger`, `compact_summary` |
|
|
418
419
|
| **ConfigChange** | `source`, `file_path` |
|
|
419
420
|
| **CwdChanged** | `old_cwd`, `new_cwd` |
|
|
421
|
+
| **DirectoryAdded** | `directory`, `source` |
|
|
420
422
|
| **FileChanged** | `file_path`, `event` |
|
|
421
423
|
| **InstructionsLoaded** | `file_path`, `load_reason` |
|
|
422
424
|
| **Elicitation** | `mcp_server_name`, `message`, `mode`, `url`, `elicitation_id`, `requested_schema` |
|
|
@@ -650,7 +652,7 @@ Claude Code hooks support multiple exit codes with different behaviors depending
|
|
|
650
652
|
> - **Blocking via top-level `decision`** (behave like `PreToolUse`/`Stop`): `UserPromptExpansion`, `PostToolBatch`, `ConfigChange`.
|
|
651
653
|
> - **Blocking via `exit 2` / `continue: false`** (no `decision` field): `TaskCreated`, `TaskCompleted`, `TeammateIdle`.
|
|
652
654
|
> - **JSON-API special** (always `exit 0`, decision in `hookSpecificOutput`): `PermissionDenied`, `Elicitation`, `ElicitationResult`, `WorktreeCreate` (bare-path stdout).
|
|
653
|
-
> - **Non-blocking / context-only** (exit code effectively ignored): `Setup`, `SubagentStart`, `PostToolUseFailure`, `StopFailure`, `PostCompact`, `CwdChanged`, `FileChanged`, `InstructionsLoaded`, `WorktreeRemove`, `MessageDisplay`.
|
|
655
|
+
> - **Non-blocking / context-only** (exit code effectively ignored): `Setup`, `SubagentStart`, `PostToolUseFailure`, `StopFailure`, `PostCompact`, `CwdChanged`, `DirectoryAdded`, `FileChanged`, `InstructionsLoaded`, `WorktreeRemove`, `MessageDisplay`.
|
|
654
656
|
|
|
655
657
|
|
|
656
658
|
#### Manually outputing and exiting example with success
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# DirectoryAdded API
|
|
2
|
+
|
|
3
|
+
Available when inheriting from `ClaudeHooks::DirectoryAdded`:
|
|
4
|
+
|
|
5
|
+
Runs after a working directory is added mid-session via `/add-dir` or the SDK `register_repo_root` control request. Non-blocking โ the directory is already added when the hook runs.
|
|
6
|
+
|
|
7
|
+
## Input Helpers
|
|
8
|
+
|
|
9
|
+
[๐ Shared input helpers](COMMON.md#input-helpers)
|
|
10
|
+
|
|
11
|
+
| Method | Description |
|
|
12
|
+
|--------|-------------|
|
|
13
|
+
| `directory` | Absolute path of the directory that was added |
|
|
14
|
+
| `source` | How the directory was added: `'slash_command'` for `/add-dir` or `'register_repo_root'` for the SDK control request |
|
|
15
|
+
|
|
16
|
+
## Hook State Helpers
|
|
17
|
+
|
|
18
|
+
[๐ Shared hook state methods](COMMON.md#hook-state-methods)
|
|
19
|
+
|
|
20
|
+
| Method | Description |
|
|
21
|
+
|--------|-------------|
|
|
22
|
+
| `system_message!` | Set a message delivered to Claude as context on the next turn (`slash_command`) or written to the debug log (`register_repo_root`) |
|
|
23
|
+
|
|
24
|
+
## Output Helpers
|
|
25
|
+
|
|
26
|
+
[๐ Shared output helpers](COMMON.md#output-helpers)
|
|
27
|
+
|
|
28
|
+
| Method | Description |
|
|
29
|
+
|--------|-------------|
|
|
30
|
+
| `output.system_message` | The configured system message |
|
|
31
|
+
|
|
32
|
+
## Hook Exit Codes
|
|
33
|
+
|
|
34
|
+
Non-blocking. Exit code is ignored; only `systemMessage` is consumed.
|
|
@@ -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, or LLM prompts that execute automatically at specific points in Claude Codeโs lifecycle. Use this reference to look up event schemas, configuration options, JSON input/output formats, and advanced features like async hooks, HTTP hooks, and MCP tool hooks.
|
|
11
|
+
Hooks are user-defined shell commands, HTTP endpoints, or LLM prompts that execute automatically at specific points in Claude Codeโs lifecycle. Hooks run wherever Claude Code runs: sessions in the terminal, IDE extensions, the [Desktop app](https://code.claude.com/docs/en/desktop-quickstart), and [Claude Code on the web](https://code.claude.com/docs/en/claude-code-on-the-web) all fire the same hook events. 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.
|
|
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
17
|
- once per session: `SessionStart` and `SessionEnd`
|
|
18
18
|
- once per turn: `UserPromptSubmit`, `Stop`, and `StopFailure`
|
|
19
|
-
- on every tool call inside the agentic loop: `PreToolUse` and `PostToolUse`
|
|
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, and MessageDisplay as a display-only event that runs while assistant message text streams](https://mintcdn.com/claude-code/jhXrDR5TrSZ5hgXM/images/hooks-lifecycle.svg?fit=max&auto=format&n=jhXrDR5TrSZ5hgXM&q=85&s=3ca47113d5956460e6e4611b8dbc63b7)
|
|
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
|
|
|
@@ -29,7 +29,7 @@ The table below summarizes when each event fires. The [Hook events](https://code
|
|
|
29
29
|
| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |
|
|
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
|
|
32
|
+
| `PermissionRequest` | When a tool call needs a permission decision |
|
|
33
33
|
| `PermissionDenied` | When a tool call is denied by the auto mode classifier. Return `{retry: true}` to tell the model it may retry the denied tool call |
|
|
34
34
|
| `PostToolUse` | After a tool call succeeds |
|
|
35
35
|
| `PostToolUseFailure` | After a tool call fails |
|
|
@@ -46,9 +46,10 @@ The table below summarizes when each event fires. The [Hook events](https://code
|
|
|
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 is being removed
|
|
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 is being removed at session exit, when a subagent finishes, or when you delete a background session |
|
|
52
53
|
| `PreCompact` | Before context compaction |
|
|
53
54
|
| `PostCompact` | After context compaction completes |
|
|
54
55
|
| `Elicitation` | When an MCP server requests user input during a tool call |
|
|
@@ -57,7 +58,14 @@ The table below summarizes when each event fires. The [Hook events](https://code
|
|
|
57
58
|
|
|
58
59
|
### [โ](https://code.claude.com/docs/en/hooks\#how-a-hook-resolves) How a hook resolves
|
|
59
60
|
|
|
60
|
-
To see how these pieces fit together, consider this `PreToolUse` hook that blocks destructive shell commands.
|
|
61
|
+
To see how these pieces fit together, consider this `PreToolUse` hook that blocks destructive shell commands.
|
|
62
|
+
|
|
63
|
+
- macOS/Linux
|
|
64
|
+
|
|
65
|
+
- Windows (PowerShell)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
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
69
|
|
|
62
70
|
```
|
|
63
71
|
{
|
|
@@ -79,7 +87,7 @@ To see how these pieces fit together, consider this `PreToolUse` hook that block
|
|
|
79
87
|
}
|
|
80
88
|
```
|
|
81
89
|
|
|
82
|
-
The script reads the JSON input from stdin, extracts the command, and returns a `permissionDecision` of `"deny"` if it contains `rm -rf
|
|
90
|
+
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
91
|
|
|
84
92
|
```
|
|
85
93
|
#!/bin/bash
|
|
@@ -99,13 +107,73 @@ else
|
|
|
99
107
|
fi
|
|
100
108
|
```
|
|
101
109
|
|
|
102
|
-
|
|
110
|
+
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
111
|
|
|
104
|
-
|
|
112
|
+
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
113
|
|
|
106
|
-
|
|
114
|
+
```
|
|
115
|
+
{
|
|
116
|
+
"hooks": {
|
|
117
|
+
"PreToolUse": [\
|
|
118
|
+
{\
|
|
119
|
+
"matcher": "Bash|PowerShell",\
|
|
120
|
+
"hooks": [\
|
|
121
|
+
{\
|
|
122
|
+
"type": "command",\
|
|
123
|
+
"if": "Bash(rm *)",\
|
|
124
|
+
"command": "powershell.exe",\
|
|
125
|
+
"args": [\
|
|
126
|
+
"-NoProfile",\
|
|
127
|
+
"-ExecutionPolicy",\
|
|
128
|
+
"Bypass",\
|
|
129
|
+
"-File",\
|
|
130
|
+
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"\
|
|
131
|
+
]\
|
|
132
|
+
},\
|
|
133
|
+
{\
|
|
134
|
+
"type": "command",\
|
|
135
|
+
"if": "PowerShell(Remove-Item *)",\
|
|
136
|
+
"command": "powershell.exe",\
|
|
137
|
+
"args": [\
|
|
138
|
+
"-NoProfile",\
|
|
139
|
+
"-ExecutionPolicy",\
|
|
140
|
+
"Bypass",\
|
|
141
|
+
"-File",\
|
|
142
|
+
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"\
|
|
143
|
+
]\
|
|
144
|
+
}\
|
|
145
|
+
]\
|
|
146
|
+
}\
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
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:
|
|
107
153
|
|
|
108
|
-
|
|
154
|
+
```
|
|
155
|
+
# .claude/hooks/block-rm.ps1
|
|
156
|
+
$callInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
157
|
+
$command = $callInput.tool_input.command
|
|
158
|
+
|
|
159
|
+
if ($command -match 'rm -rf|Remove-Item.*-Recurse') {
|
|
160
|
+
@{
|
|
161
|
+
hookSpecificOutput = @{
|
|
162
|
+
hookEventName = "PreToolUse"
|
|
163
|
+
permissionDecision = "deny"
|
|
164
|
+
permissionDecisionReason = "Destructive command blocked by hook"
|
|
165
|
+
}
|
|
166
|
+
} | ConvertTo-Json
|
|
167
|
+
} else {
|
|
168
|
+
exit 0 # no decision; normal permission flow applies
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"` against the macOS/Linux config. Hereโs what happens:
|
|
173
|
+
|
|
174
|
+

|
|
175
|
+
|
|
176
|
+
1
|
|
109
177
|
|
|
110
178
|
Event fires
|
|
111
179
|
|
|
@@ -117,24 +185,18 @@ The `PreToolUse` event fires. Claude Code sends the tool input as JSON on stdin
|
|
|
117
185
|
|
|
118
186
|
2
|
|
119
187
|
|
|
120
|
-
[Navigate to header](https://code.claude.com/docs/en/hooks#)
|
|
121
|
-
|
|
122
188
|
Matcher checks
|
|
123
189
|
|
|
124
190
|
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
191
|
|
|
126
192
|
3
|
|
127
193
|
|
|
128
|
-
[Navigate to header](https://code.claude.com/docs/en/hooks#)
|
|
129
|
-
|
|
130
194
|
If condition checks
|
|
131
195
|
|
|
132
196
|
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
197
|
|
|
134
198
|
4
|
|
135
199
|
|
|
136
|
-
[Navigate to header](https://code.claude.com/docs/en/hooks#)
|
|
137
|
-
|
|
138
200
|
Hook handler runs
|
|
139
201
|
|
|
140
202
|
The script inspects the full command and finds `rm -rf`, so it prints a decision to stdout:
|
|
@@ -153,8 +215,6 @@ If the command had been a safer `rm` variant like `rm file.txt`, the script woul
|
|
|
153
215
|
|
|
154
216
|
5
|
|
155
217
|
|
|
156
|
-
[Navigate to header](https://code.claude.com/docs/en/hooks#)
|
|
157
|
-
|
|
158
218
|
Claude Code acts on the result
|
|
159
219
|
|
|
160
220
|
Claude Code reads the JSON decision, blocks the tool call, and shows Claude the reason.
|
|
@@ -181,12 +241,15 @@ Where you define a hook determines its scope:
|
|
|
181
241
|
| --- | --- | --- |
|
|
182
242
|
| `~/.claude/settings.json` | All your projects | No, local to your machine |
|
|
183
243
|
| `.claude/settings.json` | Single project | Yes, can be committed to the repo |
|
|
184
|
-
| `.claude/settings.local.json` | Single project | No, gitignored when Claude Code
|
|
244
|
+
| `.claude/settings.local.json` | Single project | No, gitignored when Claude Code saves a setting to it |
|
|
185
245
|
| Managed policy settings | Organization-wide | Yes, admin-controlled |
|
|
186
246
|
| [Plugin](https://code.claude.com/docs/en/plugins)`hooks/hooks.json` | When plugin is enabled | Yes, bundled with the plugin |
|
|
187
247
|
| [Skill](https://code.claude.com/docs/en/skills) or [agent](https://code.claude.com/docs/en/sub-agents) frontmatter | While the component is active | Yes, defined in the component file |
|
|
188
248
|
|
|
189
|
-
For details on settings file resolution, see [settings](https://code.claude.com/docs/en/settings).Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. Hooks from plugins force-enabled in managed settings `enabledPlugins` are exempt,
|
|
249
|
+
Cloud sessions on [Claude Code on the web](https://code.claude.com/docs/en/claude-code-on-the-web) donโt read your local `~/.claude/settings.json`; hooks there come from the repo and from your organizationโs server-managed settings. See [what carries over from your setup](https://code.claude.com/docs/en/cloud-environments#what-carries-over-from-your-setup) for which files 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.Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. Hooks from plugins force-enabled in managed settings `enabledPlugins` are exempt. See [Hook configuration](https://code.claude.com/docs/en/settings#hook-configuration).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#hook-configuration) apply to hooks from every source, including managed policy settings:
|
|
250
|
+
|
|
251
|
+
- `allowedHttpHookUrls`: when defined at any settings level, Claude Code runs an HTTP hook handler only if its URL matches the merged allowlist
|
|
252
|
+
- `httpHookAllowedEnvVars`: when defined, Claude Code interpolates only the environment variables on that list into hook headers
|
|
190
253
|
|
|
191
254
|
### [โ](https://code.claude.com/docs/en/hooks\#matcher-patterns) Matcher patterns
|
|
192
255
|
|
|
@@ -203,7 +266,7 @@ A matcher on the regular-expression path is tested with JavaScriptโs `RegExp.p
|
|
|
203
266
|
| Event | What the matcher filters | Example matcher values |
|
|
204
267
|
| --- | --- | --- |
|
|
205
268
|
| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied` | tool name | `Bash`, `Edit|Write`, `mcp__.*` |
|
|
206
|
-
| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |
|
|
269
|
+
| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact`, `fork` |
|
|
207
270
|
| `Setup` | which CLI flag triggered setup | `init`, `maintenance` |
|
|
208
271
|
| `SessionEnd` | why the session ended | `clear`, `resume`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |
|
|
209
272
|
| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`, `elicitation_complete`, `elicitation_response`, `agent_needs_input`, `agent_completed` |
|
|
@@ -212,6 +275,7 @@ A matcher on the regular-expression path is tested with JavaScriptโs `RegExp.p
|
|
|
212
275
|
| `SubagentStop` | agent type | same values as `SubagentStart` |
|
|
213
276
|
| `ConfigChange` | configuration source | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` |
|
|
214
277
|
| `CwdChanged` | no matcher support | always fires on every directory change |
|
|
278
|
+
| `DirectoryAdded` | how the directory was added | `slash_command`, `register_repo_root` |
|
|
215
279
|
| `FileChanged` | literal filenames to watch (see [FileChanged](https://code.claude.com/docs/en/hooks#filechanged)) | `.envrc|.env` |
|
|
216
280
|
| `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` |
|
|
217
281
|
| `InstructionsLoaded` | load reason | `session_start`, `nested_traversal`, `path_glob_match`, `include`, `compact` |
|
|
@@ -240,7 +304,7 @@ The matcher runs against a field from the [JSON input](https://code.claude.com/d
|
|
|
240
304
|
}
|
|
241
305
|
```
|
|
242
306
|
|
|
243
|
-
|
|
307
|
+
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
308
|
|
|
245
309
|
#### [โ](https://code.claude.com/docs/en/hooks\#match-mcp-tools) Match MCP tools
|
|
246
310
|
|
|
@@ -295,7 +359,7 @@ Each object in the inner `hooks` array is a hook handler: the shell command, HTT
|
|
|
295
359
|
- **[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 a yes/no decision as JSON. See [Prompt-based hooks](https://code.claude.com/docs/en/hooks#prompt-based-hooks).
|
|
296
360
|
- **[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
361
|
|
|
298
|
-
All matching hooks run in parallel
|
|
362
|
+
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. The `$CLAUDE_CODE_REMOTE` environment variable is set to `"true"` in remote web environments and not set in the local CLI. As of v2.1.199, [`$CLAUDE_CODE_BRIDGE_SESSION_ID`](https://code.claude.com/docs/en/env-vars) is set 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
363
|
|
|
300
364
|
#### [โ](https://code.claude.com/docs/en/hooks\#common-fields) Common fields
|
|
301
365
|
|
|
@@ -305,11 +369,11 @@ These fields apply to all hook types:
|
|
|
305
369
|
| --- | --- | --- |
|
|
306
370
|
| `type` | yes | `"command"`, `"http"`, `"mcp_tool"`, `"prompt"`, or `"agent"` |
|
|
307
371
|
| `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) below 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. Uses the same syntax as [permission rules](https://code.claude.com/docs/en/permissions) |
|
|
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) lowers the `command`, `http`, and `mcp_tool` default to 30, and [`MessageDisplay`](https://code.claude.com/docs/en/hooks#messagedisplay) lowers it to 10 |
|
|
372
|
+
| `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) lowers the `command`, `http`, and `mcp_tool` default to 30, and [`MessageDisplay`](https://code.claude.com/docs/en/hooks#messagedisplay) lowers it to 10. [`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
373
|
| `statusMessage` | no | Custom spinner message displayed while the hook runs |
|
|
310
374
|
| `once` | no | If `true`, runs once per session then is removed. 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 |
|
|
311
375
|
|
|
312
|
-
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.For Bash patterns, 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.
|
|
376
|
+
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.For Bash patterns, 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
377
|
|
|
314
378
|
| `if` pattern | Bash command | Hook runs? | Why |
|
|
315
379
|
| --- | --- | --- | --- |
|
|
@@ -358,7 +422,7 @@ The equivalent shell form needs quoting to handle paths with spaces or special c
|
|
|
358
422
|
}
|
|
359
423
|
```
|
|
360
424
|
|
|
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.
|
|
425
|
+
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-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
426
|
|
|
363
427
|
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
428
|
|
|
@@ -372,7 +436,7 @@ In addition to the [common fields](https://code.claude.com/docs/en/hooks#common-
|
|
|
372
436
|
| `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
437
|
| `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 |
|
|
374
438
|
|
|
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
|
|
439
|
+
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
440
|
|
|
377
441
|
```
|
|
378
442
|
{
|
|
@@ -446,7 +510,7 @@ Use these placeholders to reference hook scripts relative to the project or plug
|
|
|
446
510
|
- `${CLAUDE_PLUGIN_ROOT}`: the pluginโs installation directory, for scripts bundled with a [plugin](https://code.claude.com/docs/en/plugins). Changes on each plugin update.
|
|
447
511
|
- `${CLAUDE_PLUGIN_DATA}`: the pluginโs [persistent data directory](https://code.claude.com/docs/en/plugins-reference#persistent-data-directory), for dependencies and state that should survive plugin updates.
|
|
448
512
|
|
|
449
|
-
Prefer [exec form](https://code.claude.com/docs/en/hooks#exec-form-and-shell-form) for any hook that references a path placeholder.
|
|
513
|
+
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
514
|
|
|
451
515
|
- Project scripts
|
|
452
516
|
|
|
@@ -516,18 +580,18 @@ hooks:
|
|
|
516
580
|
---
|
|
517
581
|
```
|
|
518
582
|
|
|
519
|
-
|
|
583
|
+
Subagents use the same format in their YAML frontmatter.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; see [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
584
|
|
|
521
585
|
### [โ](https://code.claude.com/docs/en/hooks\#the-/hooks-menu) The `/hooks` menu
|
|
522
586
|
|
|
523
587
|
Type `/hooks` in Claude Code to open a read-only browser for your configured hooks. The menu shows every hook event with a count of configured hooks, lets you drill into matchers, and shows the full details of each hook handler. Use it to verify configuration, check which settings file a hook came from, or inspect a hookโs command, prompt, or URL.The menu displays all five hook types: `command`, `prompt`, `agent`, `http`, and `mcp_tool`. Each hook is labeled with a `[type]` prefix and a source indicating where it was defined:
|
|
524
588
|
|
|
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
|
|
589
|
+
- `User Settings`: from `~/.claude/settings.json`
|
|
590
|
+
- `Project Settings`: from `.claude/settings.json`
|
|
591
|
+
- `Local Settings`: from `.claude/settings.local.json`
|
|
592
|
+
- `Plugin Hooks`: from a pluginโs `hooks/hooks.json`
|
|
593
|
+
- `Session Hooks`: registered in memory for the current session
|
|
594
|
+
- `Built-in Hooks`: registered internally by Claude Code
|
|
531
595
|
|
|
532
596
|
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.
|
|
533
597
|
|
|
@@ -558,9 +622,9 @@ When running with `--agent` or inside a subagent, two additional fields are incl
|
|
|
558
622
|
| Field | Description |
|
|
559
623
|
| --- | --- |
|
|
560
624
|
| `agent_id` | Unique identifier for the subagent. Present only when the hook fires inside a subagent call. Use this to distinguish subagent hook calls from main-thread calls. |
|
|
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.
|
|
625
|
+
| `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
626
|
|
|
563
|
-
Only [`SessionStart`](https://code.claude.com/docs/en/hooks#sessionstart) hooks can receive a `model` field, and it is not guaranteed to be present. There is no `$CLAUDE_MODEL` environment variable. A hook process inherits the parent environment, so it 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.For example, a `PreToolUse` hook for a Bash command receives this on stdin:
|
|
627
|
+
Only [`SessionStart`](https://code.claude.com/docs/en/hooks#sessionstart) hooks can receive a `model` field, and it is not guaranteed to be present. There is no `$CLAUDE_MODEL` environment variable. A hook process inherits the parent environment, so it 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. One set of variables is not inherited: Claude Code [removes `OTEL_*` exporter variables from every subprocess it spawns](https://code.claude.com/docs/en/monitoring-usage#administrator-configuration), including hooks.For example, a `PreToolUse` hook for a Bash command receives this on stdin:
|
|
564
628
|
|
|
565
629
|
```
|
|
566
630
|
{
|
|
@@ -572,21 +636,26 @@ Only [`SessionStart`](https://code.claude.com/docs/en/hooks#sessionstart) hooks
|
|
|
572
636
|
"hook_event_name": "PreToolUse",
|
|
573
637
|
"tool_name": "Bash",
|
|
574
638
|
"tool_input": {
|
|
575
|
-
"command": "npm test"
|
|
576
|
-
|
|
639
|
+
"command": "npm test",
|
|
640
|
+
"description": "Run test suite",
|
|
641
|
+
"timeout": 120000,
|
|
642
|
+
"run_in_background": false
|
|
643
|
+
},
|
|
644
|
+
"tool_use_id": "toolu_01ABC123..."
|
|
577
645
|
}
|
|
578
646
|
```
|
|
579
647
|
|
|
580
|
-
The `tool_name` and `
|
|
648
|
+
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
649
|
|
|
582
650
|
### [โ](https://code.claude.com/docs/en/hooks\#exit-code-output) Exit code output
|
|
583
651
|
|
|
584
|
-
The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.**Exit 0** means success. Claude Code parses stdout for [JSON output fields](https://code.claude.com/docs/en/hooks#json-output). JSON output is only processed on exit 0. For most events, stdout is written to the debug log but not shown in the transcript. The exceptions are `UserPromptSubmit`, `UserPromptExpansion`, and `SessionStart`, where stdout is added as context that Claude can see and act on.**Exit 2** means a blocking error. Claude Code ignores stdout and any JSON in it. Instead, stderr text is fed back to Claude as an error message. The effect depends on the event: `PreToolUse` blocks the tool call, `UserPromptSubmit` rejects the prompt, and so on. See [exit code 2 behavior](https://code.claude.com/docs/en/hooks#exit-code-2-behavior-per-event) for the full list.**Any other exit code** is a non-blocking error for most hook events. The transcript shows a `<hook name> hook error` notice followed by the first line of stderr,
|
|
652
|
+
The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.**Exit 0** means success. Claude Code parses stdout for [JSON output fields](https://code.claude.com/docs/en/hooks#json-output). JSON output is only processed on exit 0. For most events, stdout is written to the debug log but not shown in the transcript. The exceptions are `UserPromptSubmit`, `UserPromptExpansion`, and `SessionStart`, where stdout is added as context that Claude can see and act on.Stderr from a hook that exits 0 goes to the debug log only, never the transcript, and Claude never sees it. To read it yourself, 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.**Exit 2** means a blocking error. Claude Code ignores stdout and any JSON in it. Instead, stderr text is fed back to Claude as an error message. The effect depends on the event: `PreToolUse` blocks the tool call, `UserPromptSubmit` rejects the prompt, and so on. See [exit code 2 behavior](https://code.claude.com/docs/en/hooks#exit-code-2-behavior-per-event) for the full list.A hook that exits 2 while printing JSON that fails [JSON output](https://code.claude.com/docs/en/hooks#json-output) schema validation still blocks: Claude Code uses stderr as the blocking reason and records the validation failure in the debug log. Before v2.1.214, Claude Code treated that combination as a non-blocking error and the action proceeded.**Any other exit code** is a non-blocking error for most hook events. The action proceeds, and the transcript shows a `<hook name> hook error` notice followed by the first line of stderr, prefixed with `Failed with non-blocking status code:`. To capture the full stderr, enable [debug logging](https://code.claude.com/docs/en/hooks#debug-hooks).For example, a hook command script that blocks dangerous Bash commands:
|
|
585
653
|
|
|
586
654
|
```
|
|
587
655
|
#!/bin/bash
|
|
588
656
|
# Reads JSON input from stdin, checks the command
|
|
589
|
-
|
|
657
|
+
input=$(cat)
|
|
658
|
+
command=$(jq -r '.tool_input.command' <<<"$input")
|
|
590
659
|
|
|
591
660
|
if [[ "$command" == rm* ]]; then
|
|
592
661
|
echo "Blocked: rm commands are not allowed" >&2
|
|
@@ -625,6 +694,7 @@ Exit code 2 is the way a hook signals โstop, donโt do this.โ The effect de
|
|
|
625
694
|
| `Setup` | No | Shows stderr to user only |
|
|
626
695
|
| `SessionEnd` | No | Shows stderr to user only |
|
|
627
696
|
| `CwdChanged` | No | Shows stderr to user only |
|
|
697
|
+
| `DirectoryAdded` | No | Stderr goes to the debug log; the directory is already added |
|
|
628
698
|
| `FileChanged` | No | Shows stderr to user only |
|
|
629
699
|
| `PreCompact` | Yes | Blocks compaction |
|
|
630
700
|
| `PostCompact` | No | Shows stderr to user only |
|
|
@@ -635,7 +705,7 @@ Exit code 2 is the way a hook signals โstop, donโt do this.โ The effect de
|
|
|
635
705
|
| `InstructionsLoaded` | No | Exit code is ignored |
|
|
636
706
|
| `MessageDisplay` | No | The original text is displayed |
|
|
637
707
|
|
|
638
|
-
For `SessionStart`, `Setup`, and `SubagentStart`, the exit code 2 stderr renders in the transcript as a `<hook name> hook error` notice, the same way a [non-blocking error](https://code.claude.com/docs/en/hooks#exit-code-output) does. 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.
|
|
708
|
+
For `SessionStart`, `Setup`, and `SubagentStart`, the exit code 2 stderr renders in the transcript as a `<hook name> hook error` notice, the same way a [non-blocking error](https://code.claude.com/docs/en/hooks#exit-code-output) does. 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
709
|
|
|
640
710
|
### [โ](https://code.claude.com/docs/en/hooks\#http-response-handling) HTTP response handling
|
|
641
711
|
|
|
@@ -655,7 +725,7 @@ Exit codes only let you block or stay silent, but JSON output gives you finer-gr
|
|
|
655
725
|
|
|
656
726
|
You must choose one approach per hook, not both: either use exit codes alone for signaling, or exit 0 and print JSON for structured control. Claude Code only processes JSON on exit 0. If you exit 2, any JSON is ignored.
|
|
657
727
|
|
|
658
|
-
Your hookโs stdout must contain only the JSON object. If your shell profile prints text on startup, it can interfere with JSON parsing. See [JSON validation failed](https://code.claude.com/docs/en/hooks-guide#json-validation-failed) in the troubleshooting guide.Hook output strings, including `additionalContext`, `systemMessage`, and plain stdout, are capped at 10,000 characters. Output that exceeds this limit is saved to a file and replaced with a preview and file path, the same way large
|
|
728
|
+
Your hookโs stdout must contain only the JSON object. If your shell profile prints text on startup, it can interfere with JSON parsing. See [JSON validation failed](https://code.claude.com/docs/en/hooks-guide#json-validation-failed) in the troubleshooting guide.Hook output strings, including `additionalContext`, `systemMessage`, and plain stdout, are capped at 10,000 characters. Output that exceeds this limit is saved to a file and replaced with a preview and file path, the same way a large valid Bash result is handled under [Output limits](https://code.claude.com/docs/en/tools-reference#output-limits).The JSON object supports three kinds of fields:
|
|
659
729
|
|
|
660
730
|
- **Universal fields** like `continue` work across all events. These are listed in the table below.
|
|
661
731
|
- **Top-level `decision` and `reason`** are used by some events to block or provide feedback.
|
|
@@ -675,6 +745,8 @@ To stop Claude entirely regardless of event type:
|
|
|
675
745
|
{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
|
|
676
746
|
```
|
|
677
747
|
|
|
748
|
+
For `PreToolUse` and `PostToolUse` hooks, the stop applies even when the tool call fails or completes while Claude is still streaming a response.
|
|
749
|
+
|
|
678
750
|
#### [โ](https://code.claude.com/docs/en/hooks\#emit-terminal-notifications) Emit terminal notifications
|
|
679
751
|
|
|
680
752
|
The `terminalSequence` field requires Claude Code v2.1.141 or later.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:
|
|
@@ -727,7 +799,7 @@ When several hooks return `additionalContext` for the same event, Claude receive
|
|
|
727
799
|
- **Conditional project rules**: which test command applies to the file just edited, which directories are read-only in this worktree
|
|
728
800
|
- **External data**: open issues assigned to you, recent CI results, content fetched from an internal service
|
|
729
801
|
|
|
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.
|
|
802
|
+
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
803
|
|
|
732
804
|
#### [โ](https://code.claude.com/docs/en/hooks\#decision-control) Decision control
|
|
733
805
|
|
|
@@ -745,7 +817,7 @@ Not every event supports blocking or controlling behavior through JSON. The even
|
|
|
745
817
|
| ElicitationResult | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values override) |
|
|
746
818
|
| MessageDisplay | `hookSpecificOutput` | `displayContent` replaces the displayed text on screen. Display-only: the transcript and what Claude sees keep the original |
|
|
747
819
|
| SessionStart, Setup, SubagentStart | 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 |
|
|
748
|
-
| WorktreeRemove, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, FileChanged | None | No decision control. Used for side effects like logging or cleanup |
|
|
820
|
+
| WorktreeRemove, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, DirectoryAdded, FileChanged | None | No decision control. Used for side effects like logging or cleanup |
|
|
749
821
|
|
|
750
822
|
A few events can also rewrite content rather than only allow or block it:
|
|
751
823
|
|
|
@@ -763,7 +835,7 @@ For redaction or transformation use cases, intercept at `PreToolUse` for outboun
|
|
|
763
835
|
- PermissionRequest
|
|
764
836
|
|
|
765
837
|
|
|
766
|
-
|
|
838
|
+
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
839
|
|
|
768
840
|
```
|
|
769
841
|
{
|
|
@@ -816,6 +888,9 @@ Runs when Claude Code starts a new session or resumes an existing session. Usefu
|
|
|
816
888
|
| `resume` | `--resume`, `--continue`, or `/resume` |
|
|
817
889
|
| `clear` | `/clear` |
|
|
818
890
|
| `compact` | Auto or manual compaction |
|
|
891
|
+
| `fork` | A new session forked from an existing one: `--fork-session` with `--resume` or `--continue`, the `/fork` background copy, or `/branch` |
|
|
892
|
+
|
|
893
|
+
Before v2.1.214, forked sessions reported source `"resume"`.
|
|
819
894
|
|
|
820
895
|
#### [โ](https://code.claude.com/docs/en/hooks\#sessionstart-input) SessionStart input
|
|
821
896
|
|
|
@@ -823,7 +898,7 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
823
898
|
|
|
824
899
|
| Field | Description |
|
|
825
900
|
| --- | --- |
|
|
826
|
-
| `source` | How the session started: `"startup"` for new sessions, `"resume"` for resumed sessions, `"clear"` after `/clear`,
|
|
901
|
+
| `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
902
|
| `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
903
|
| `agent_type` | The agent name, present when you start Claude Code with `claude --agent <name>` |
|
|
829
904
|
| `session_title` | The current session title if one is already set, for example via `--name` or `/rename`. A hook that emits `sessionTitle` can check `session_title` first to avoid overwriting a title the user set explicitly |
|
|
@@ -847,7 +922,7 @@ Any text your hook script prints to stdout is added as context for Claude. In ad
|
|
|
847
922
|
| --- | --- |
|
|
848
923
|
| `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
924
|
| `initialUserMessage` | String used as the first user message of the session. Applies in [non-interactive mode](https://code.claude.com/docs/en/headless) with the `-p` flag, where it becomes the first turn even if no prompt is provided. If a prompt is provided, it follows as the next turn. Unlike `additionalContext`, which attaches to an existing turn, this creates the turn |
|
|
850
|
-
| `sessionTitle` | Sets the session title, with the same effect as `/rename`. Use to name sessions automatically from the launch folder, git branch, or worktree name. Applies
|
|
925
|
+
| `sessionTitle` | Sets the session title, with the same effect as `/rename`. Use to name sessions automatically from the launch folder, git branch, or worktree name. Applies when `source` is `"startup"`, `"resume"`, or `"fork"`; ignored on `"clear"` and `"compact"` |
|
|
851
926
|
| `watchPaths` | Array of absolute paths to watch for [FileChanged](https://code.claude.com/docs/en/hooks#filechanged) events during this session |
|
|
852
927
|
| `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, so skills the hook installed are available in the same session, starting with the first prompt |
|
|
853
928
|
|
|
@@ -872,6 +947,8 @@ git -C ~/.claude/skills/team-skills pull --quiet 2>/dev/null || \
|
|
|
872
947
|
echo '{"hookSpecificOutput": {"hookEventName": "SessionStart", "reloadSkills": true}}'
|
|
873
948
|
```
|
|
874
949
|
|
|
950
|
+
The repository URL is a placeholder; replace it with your own skills repository. With the placeholder, the clone fails and prints a `fatal:` message to stderr. Stderr from a SessionStart hook that exits 0 is informational only, so the `reloadSkills` request still applies.
|
|
951
|
+
|
|
875
952
|
#### [โ](https://code.claude.com/docs/en/hooks\#persist-environment-variables) Persist environment variables
|
|
876
953
|
|
|
877
954
|
SessionStart hooks have access to the `CLAUDE_ENV_FILE` environment variable, which provides a file path where you can persist environment variables for subsequent Bash commands.To set individual environment variables, write `export` statements to `CLAUDE_ENV_FILE`. Use append (`>>`) to preserve variables set by other hooks:
|
|
@@ -907,8 +984,6 @@ fi
|
|
|
907
984
|
exit 0
|
|
908
985
|
```
|
|
909
986
|
|
|
910
|
-
Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.
|
|
911
|
-
|
|
912
987
|
`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 types donโt have access to this variable.
|
|
913
988
|
|
|
914
989
|
### [โ](https://code.claude.com/docs/en/hooks\#setup) Setup
|
|
@@ -920,7 +995,7 @@ Fires only when you launch Claude Code with `--init-only`, or with `--init` or `
|
|
|
920
995
|
| `init` | `claude --init-only` or `claude -p --init` |
|
|
921
996
|
| `maintenance` | `claude -p --maintenance` |
|
|
922
997
|
|
|
923
|
-
|
|
998
|
+
When you run `claude --init-only`, Claude Code runs Setup hooks and `SessionStart` hooks with the `startup` matcher, then exits without starting a conversation.`--init` and `--maintenance` fire Setup hooks only when you combine them with `-p`. In an interactive session, those two flags donโt currently fire Setup hooks.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-reference#persistent-data-directory) for where to store installed dependencies.
|
|
924
999
|
|
|
925
1000
|
#### [โ](https://code.claude.com/docs/en/hooks\#setup-input) Setup input
|
|
926
1001
|
|
|
@@ -992,7 +1067,7 @@ InstructionsLoaded hooks have no decision control. They canโt block or modify
|
|
|
992
1067
|
|
|
993
1068
|
Runs when the user submits a prompt, before Claude processes it. This allows you
|
|
994
1069
|
to add additional context based on the prompt/conversation, validate prompts, or
|
|
995
|
-
block certain types of prompts.`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.A `UserPromptSubmit` hook that reaches its timeout is canceled and its output, including any `additionalContext`, is discarded. The prompt still reaches Claude without that context.
|
|
1070
|
+
block certain types of prompts.`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.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. 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
1071
|
|
|
997
1072
|
#### [โ](https://code.claude.com/docs/en/hooks\#userpromptsubmit-input) UserPromptSubmit input
|
|
998
1073
|
|
|
@@ -1160,8 +1235,6 @@ Save this script to `.claude/hooks/plain-display.sh` in your project and make it
|
|
|
1160
1235
|
jq '{hookSpecificOutput: {hookEventName: "MessageDisplay", displayContent: (.delta | gsub("\\*\\*"; "") | gsub("`"; ""))}}'
|
|
1161
1236
|
```
|
|
1162
1237
|
|
|
1163
|
-
The script needs `jq` on your `PATH`.
|
|
1164
|
-
|
|
1165
1238
|
Register a command hook that runs the script through PowerShell:
|
|
1166
1239
|
|
|
1167
1240
|
```
|
|
@@ -1205,15 +1278,36 @@ Batches with no markdown pass through unchanged. If the script fails, for exampl
|
|
|
1205
1278
|
|
|
1206
1279
|
### [โ](https://code.claude.com/docs/en/hooks\#pretooluse) PreToolUse
|
|
1207
1280
|
|
|
1208
|
-
Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name: `Bash`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Agent`, `WebFetch`, `WebSearch`, `AskUserQuestion`, `ExitPlanMode`, and any [MCP tool names](https://code.claude.com/docs/en/hooks#match-mcp-tools).
|
|
1281
|
+
Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name: `Bash`, `PowerShell`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Agent`, `WebFetch`, `WebSearch`, `AskUserQuestion`, `ExitPlanMode`, and any [MCP tool names](https://code.claude.com/docs/en/hooks#match-mcp-tools).
|
|
1209
1282
|
|
|
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.
|
|
1283
|
+
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
1284
|
|
|
1212
|
-
Use [PreToolUse decision control](https://code.claude.com/docs/en/hooks#pretooluse-decision-control) to allow, deny, ask, or defer the tool call.
|
|
1285
|
+
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
1286
|
|
|
1214
1287
|
#### [โ](https://code.claude.com/docs/en/hooks\#pretooluse-input) PreToolUse input
|
|
1215
1288
|
|
|
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`.
|
|
1289
|
+
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 the file tools `Write`, `Edit`, and `Read`, `tool_input.file_path` is always absolute:
|
|
1290
|
+
|
|
1291
|
+
- 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
|
|
1292
|
+
- On Windows, the path arrives with backslash separators, even when your hook runs under Git Bash where `$PWD` looks like `/c/project`
|
|
1293
|
+
- 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
|
|
1294
|
+
- 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
|
|
1295
|
+
|
|
1296
|
+
A `Write` call on Windows delivers:
|
|
1297
|
+
|
|
1298
|
+
```
|
|
1299
|
+
{
|
|
1300
|
+
"hook_event_name": "PreToolUse",
|
|
1301
|
+
"tool_name": "Write",
|
|
1302
|
+
"tool_input": {
|
|
1303
|
+
"file_path": "C:\\project\\src\\index.ts",
|
|
1304
|
+
"content": "..."
|
|
1305
|
+
},
|
|
1306
|
+
...
|
|
1307
|
+
}
|
|
1308
|
+
```
|
|
1309
|
+
|
|
1310
|
+
The `tool_input` fields depend on the tool:
|
|
1217
1311
|
|
|
1218
1312
|
##### Bash
|
|
1219
1313
|
|
|
@@ -1223,9 +1317,26 @@ Executes shell commands.
|
|
|
1223
1317
|
| --- | --- | --- | --- |
|
|
1224
1318
|
| `command` | string | `"npm test"` | The shell command to execute |
|
|
1225
1319
|
| `description` | string | `"Run test suite"` | Optional description of what the command does |
|
|
1320
|
+
| `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 |
|
|
1321
|
+
| `run_in_background` | boolean | `false` | Whether to run the command in background |
|
|
1322
|
+
|
|
1323
|
+
##### PowerShell
|
|
1324
|
+
|
|
1325
|
+
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`:
|
|
1326
|
+
|
|
1327
|
+
| Field | Type | Example | Description |
|
|
1328
|
+
| --- | --- | --- | --- |
|
|
1329
|
+
| `command` | string | `"Get-ChildItem -Recurse"` | The PowerShell command to execute |
|
|
1330
|
+
| `description` | string | `"List files recursively"` | Optional description of what the command does |
|
|
1226
1331
|
| `timeout` | number | `120000` | Optional timeout in milliseconds |
|
|
1227
1332
|
| `run_in_background` | boolean | `false` | Whether to run the command in background |
|
|
1228
1333
|
|
|
1334
|
+
Match `Bash|PowerShell` in hooks that inspect shell commands, so they cover both tools:
|
|
1335
|
+
|
|
1336
|
+
- On Windows, wherever the PowerShell tool is enabled, Claude treats PowerShell as the primary shell and routes shell commands through it.
|
|
1337
|
+
- On Windows without Git Bash, the tool is enabled automatically and Claude Code doesnโt register the Bash tool at all.
|
|
1338
|
+
- A hook that matches only `Bash` never fires there.
|
|
1339
|
+
|
|
1229
1340
|
##### Write
|
|
1230
1341
|
|
|
1231
1342
|
Creates or overwrites a file.
|
|
@@ -1315,13 +1426,14 @@ In `PostToolUse`, `tool_response` for a completed Agent call carries the subagen
|
|
|
1315
1426
|
| `status` | string | `"completed"` | `"completed"` for foreground subagents, `"async_launched"` for background subagents. As of v2.1.198, subagents run in the background by default, so an omitted `run_in_background` also produces `"async_launched"` |
|
|
1316
1427
|
| `agentId` | string | `"a4d2c8f1e0b3a297"` | Identifier for the subagent run |
|
|
1317
1428
|
| `content` | array | `[{"type": "text", "text": "Found 12 endpoints..."}]` | The subagentโs final text blocks |
|
|
1318
|
-
| `resolvedModel` | string | `"claude-sonnet-4-5"` | Model the subagent
|
|
1429
|
+
| `resolvedModel` | string | `"claude-sonnet-4-5"` | Model the subagent started on, which may differ from the requested model. Requires Claude Code v2.1.174 or later |
|
|
1430
|
+
| `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 |
|
|
1319
1431
|
| `totalTokens` | number | `12450` | Total tokens billed across the subagentโs turns |
|
|
1320
1432
|
| `totalDurationMs` | number | `48211` | Wall-clock duration of the subagent run |
|
|
1321
1433
|
| `totalToolUseCount` | number | `7` | Count of tool calls the subagent made |
|
|
1322
1434
|
| `usage` | object | `{"input_tokens": 8320, ...}` | Per-type token breakdown: `input_tokens`, `output_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens` |
|
|
1323
1435
|
|
|
1324
|
-
For background subagents, the tool returns
|
|
1436
|
+
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. It requires Claude Code v2.1.174 or later. 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
1437
|
|
|
1326
1438
|
##### AskUserQuestion
|
|
1327
1439
|
|
|
@@ -1350,12 +1462,12 @@ In `PostToolUse`, `tool_response` is an object with `plan` and `filePath` fields
|
|
|
1350
1462
|
|
|
1351
1463
|
| Field | Description |
|
|
1352
1464
|
| --- | --- |
|
|
1353
|
-
| `permissionDecision` | `"allow"` skips the permission prompt, except for [tools that require user interaction](https://code.claude.com/docs/en/hooks#pretooluse-decision-control). `"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 |
|
|
1465
|
+
| `permissionDecision` | `"allow"` skips the permission prompt, except for [tools that require user interaction](https://code.claude.com/docs/en/hooks#pretooluse-decision-control) and connector tools [your organization set to `ask`](https://code.claude.com/docs/en/mcp#organization-controls-on-connector-tools). `"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 |
|
|
1354
1466
|
| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude. For `"defer"`, ignored |
|
|
1355
1467
|
| `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 |
|
|
1356
1468
|
| `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
1469
|
|
|
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 example, `[User]`, `[Project]`, `[Plugin]`, or `[Local]`. This helps users understand which configuration source is requesting confirmation.
|
|
1470
|
+
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 example, `[User]`, `[Project]`, `[Plugin]`, or `[Local]`. 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
1471
|
|
|
1360
1472
|
```
|
|
1361
1473
|
{
|
|
@@ -1377,11 +1489,7 @@ PreToolUse previously used top-level `decision` and `reason` fields, but these a
|
|
|
1377
1489
|
|
|
1378
1490
|
#### [โ](https://code.claude.com/docs/en/hooks\#defer-a-tool-call-for-later) Defer a tool call for later
|
|
1379
1491
|
|
|
1380
|
-
`"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.
|
|
1381
|
-
|
|
1382
|
-
The `defer` value requires Claude Code v2.1.89 or later. Earlier versions donโt recognize it and the tool proceeds through the normal permission flow.
|
|
1383
|
-
|
|
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:
|
|
1492
|
+
`"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. The round trip works like this:
|
|
1385
1493
|
|
|
1386
1494
|
1. Claude calls `AskUserQuestion`. The `PreToolUse` hook fires.
|
|
1387
1495
|
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.
|
|
@@ -1407,16 +1515,16 @@ The `deferred_tool_use` field carries the toolโs `id`, `name`, and `input`. Th
|
|
|
1407
1515
|
|
|
1408
1516
|
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#available-settings) retention sweep that deletes session files after 30 days by default. If the answer is not ready when you resume, the hook can return `"defer"` again and the process exits the same way. The calling process controls when to break the loop by eventually returning `"allow"` or `"deny"` from the hook.`"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
1517
|
|
|
1410
|
-
`--resume` restores the permission mode that was active when the tool was deferred, so you donโt need to pass `--permission-mode` again. The exceptions are `plan` and `bypassPermissions`, which are never carried over. Passing `--permission-mode` explicitly on resume overrides the restored value.
|
|
1518
|
+
`--resume` restores the permission mode that was active when the tool was deferred, so you donโt need to pass `--permission-mode` again. The exceptions are `plan` and `bypassPermissions`, which are never carried over, and `auto`, which is restored only when your account still meets the [auto mode requirements](https://code.claude.com/docs/en/permission-modes#eliminate-prompts-with-auto-mode). Passing `--permission-mode` explicitly on resume overrides the restored value.
|
|
1411
1519
|
|
|
1412
1520
|
### [โ](https://code.claude.com/docs/en/hooks\#permissionrequest) PermissionRequest
|
|
1413
1521
|
|
|
1414
|
-
Runs when
|
|
1522
|
+
Runs when Claude Code is about to ask you for permission. 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.
|
|
1415
1523
|
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.
|
|
1416
1524
|
|
|
1417
1525
|
#### [โ](https://code.claude.com/docs/en/hooks\#permissionrequest-input) PermissionRequest input
|
|
1418
1526
|
|
|
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 โalways allowโ options the user would normally see in the permission dialog.
|
|
1527
|
+
PermissionRequest hooks receive `tool_name` and `tool_input` fields like PreToolUse hooks, but without `tool_use_id`. An optional `permission_suggestions` array contains the โalways allowโ options the user would normally see in the permission dialog.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
1528
|
|
|
1421
1529
|
```
|
|
1422
1530
|
{
|
|
@@ -1499,7 +1607,7 @@ Runs immediately after a tool completes successfully.Matches on tool name, same
|
|
|
1499
1607
|
|
|
1500
1608
|
#### [โ](https://code.claude.com/docs/en/hooks\#posttooluse-input) PostToolUse input
|
|
1501
1609
|
|
|
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.
|
|
1610
|
+
`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.
|
|
1503
1611
|
|
|
1504
1612
|
```
|
|
1505
1613
|
{
|
|
@@ -1559,11 +1667,13 @@ The example below replaces the output of a `Bash` call. The replacement value ma
|
|
|
1559
1667
|
|
|
1560
1668
|
### [โ](https://code.claude.com/docs/en/hooks\#posttoolusefailure) PostToolUseFailure
|
|
1561
1669
|
|
|
1562
|
-
Runs when a tool
|
|
1670
|
+
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.
|
|
1671
|
+
|
|
1672
|
+
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
1673
|
|
|
1564
1674
|
#### [โ](https://code.claude.com/docs/en/hooks\#posttoolusefailure-input) PostToolUseFailure input
|
|
1565
1675
|
|
|
1566
|
-
PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as PostToolUse, along with error information as top-level fields:
|
|
1676
|
+
PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as PostToolUse, along with error information as top-level fields. For example, a failed `npm test` command might deliver:
|
|
1567
1677
|
|
|
1568
1678
|
```
|
|
1569
1679
|
{
|
|
@@ -1578,7 +1688,7 @@ PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as
|
|
|
1578
1688
|
"description": "Run test suite"
|
|
1579
1689
|
},
|
|
1580
1690
|
"tool_use_id": "toolu_01ABC123...",
|
|
1581
|
-
"error": "
|
|
1691
|
+
"error": "Exit code 1\nError: Cannot find module 'express'",
|
|
1582
1692
|
"is_interrupt": false,
|
|
1583
1693
|
"duration_ms": 4187
|
|
1584
1694
|
}
|
|
@@ -1586,10 +1696,16 @@ PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as
|
|
|
1586
1696
|
|
|
1587
1697
|
| Field | Description |
|
|
1588
1698
|
| --- | --- |
|
|
1589
|
-
| `error` | String describing what went wrong |
|
|
1590
|
-
| `is_interrupt` | Optional boolean
|
|
1699
|
+
| `error` | String describing what went wrong. The format depends on the tool that failed |
|
|
1700
|
+
| `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
1701
|
| `duration_ms` | Optional. Tool execution time in milliseconds. Excludes time spent in permission prompts and PreToolUse hooks |
|
|
1592
1702
|
|
|
1703
|
+
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.
|
|
1704
|
+
|
|
1705
|
+
- 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
|
|
1706
|
+
- A payload may also carry a bare failure message with no exit-code line, when Claude Code could not start the shell process itself
|
|
1707
|
+
- Claude Code middle-truncates strings longer than 10,000 characters around a `... [N characters truncated] ...` marker, and can insert lines of its own, such as `Command timed out after 2m 0s`
|
|
1708
|
+
|
|
1593
1709
|
#### [โ](https://code.claude.com/docs/en/hooks\#posttoolusefailure-decision-control) PostToolUseFailure decision control
|
|
1594
1710
|
|
|
1595
1711
|
`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:
|
|
@@ -1683,13 +1799,13 @@ In addition to the [common input fields](https://code.claude.com/docs/en/hooks#c
|
|
|
1683
1799
|
"description": "Clean build directory"
|
|
1684
1800
|
},
|
|
1685
1801
|
"tool_use_id": "toolu_01ABC123...",
|
|
1686
|
-
"reason": "
|
|
1802
|
+
"reason": "Blocked by classifier"
|
|
1687
1803
|
}
|
|
1688
1804
|
```
|
|
1689
1805
|
|
|
1690
1806
|
| Field | Description |
|
|
1691
1807
|
| --- | --- |
|
|
1692
|
-
| `reason` | The classifierโs explanation
|
|
1808
|
+
| `reason` | The denial reason: the fixed text `Blocked by classifier` in most sessions, or the classifierโs written explanation when the sessionโs classifier model provides one. See [Review denials](https://code.claude.com/docs/en/auto-mode-config#review-denials) |
|
|
1693
1809
|
|
|
1694
1810
|
#### [โ](https://code.claude.com/docs/en/hooks\#permissiondenied-decision-control) PermissionDenied decision control
|
|
1695
1811
|
|
|
@@ -1831,7 +1947,7 @@ SubagentStop hooks use the same decision control format as [Stop hooks](https://
|
|
|
1831
1947
|
|
|
1832
1948
|
### [โ](https://code.claude.com/docs/en/hooks\#taskcreated) TaskCreated
|
|
1833
1949
|
|
|
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.
|
|
1950
|
+
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.TaskCreated hooks donโt support matchers and fire on every occurrence.
|
|
1835
1951
|
|
|
1836
1952
|
#### [โ](https://code.claude.com/docs/en/hooks\#taskcreated-input) TaskCreated input
|
|
1837
1953
|
|
|
@@ -1884,7 +2000,7 @@ exit 0
|
|
|
1884
2000
|
|
|
1885
2001
|
### [โ](https://code.claude.com/docs/en/hooks\#taskcompleted) TaskCompleted
|
|
1886
2002
|
|
|
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.
|
|
2003
|
+
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
2004
|
|
|
1889
2005
|
#### [โ](https://code.claude.com/docs/en/hooks\#taskcompleted-input) TaskCompleted input
|
|
1890
2006
|
|
|
@@ -2058,7 +2174,7 @@ StopFailure hooks have no decision control. They run for notification and loggin
|
|
|
2058
2174
|
|
|
2059
2175
|
### [โ](https://code.claude.com/docs/en/hooks\#teammateidle) TeammateIdle
|
|
2060
2176
|
|
|
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.
|
|
2177
|
+
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
2178
|
|
|
2063
2179
|
#### [โ](https://code.claude.com/docs/en/hooks\#teammateidle-input) TeammateIdle input
|
|
2064
2180
|
|
|
@@ -2195,6 +2311,46 @@ In addition to the [JSON output fields](https://code.claude.com/docs/en/hooks#js
|
|
|
2195
2311
|
|
|
2196
2312
|
CwdChanged hooks have no decision control. They canโt block the directory change.
|
|
2197
2313
|
|
|
2314
|
+
### [โ](https://code.claude.com/docs/en/hooks\#directoryadded) DirectoryAdded
|
|
2315
|
+
|
|
2316
|
+
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:
|
|
2317
|
+
|
|
2318
|
+
- You pass a directory with the `--add-dir` startup flag; [SessionStart](https://code.claude.com/docs/en/hooks#sessionstart) covers those directories
|
|
2319
|
+
- You add a directory on the `/permissions` Workspace tab
|
|
2320
|
+
- You add a directory that is already a working directory; the add fails with an error
|
|
2321
|
+
|
|
2322
|
+
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:
|
|
2323
|
+
|
|
2324
|
+
| Matcher | When it fires |
|
|
2325
|
+
| --- | --- |
|
|
2326
|
+
| `slash_command` | You add a directory with `/add-dir` |
|
|
2327
|
+
| `register_repo_root` | An SDK client adds a directory with the `register_repo_root` control request |
|
|
2328
|
+
|
|
2329
|
+
#### [โ](https://code.claude.com/docs/en/hooks\#directoryadded-input) DirectoryAdded input
|
|
2330
|
+
|
|
2331
|
+
In addition to the [common input fields](https://code.claude.com/docs/en/hooks#common-input-fields), DirectoryAdded hooks receive `directory` and `source`.
|
|
2332
|
+
|
|
2333
|
+
| Field | Description |
|
|
2334
|
+
| --- | --- |
|
|
2335
|
+
| `directory` | Absolute path of the directory that was added |
|
|
2336
|
+
| `source` | How the directory was added, `"slash_command"` for `/add-dir` or `"register_repo_root"` for the SDK control request |
|
|
2337
|
+
|
|
2338
|
+
```
|
|
2339
|
+
{
|
|
2340
|
+
"session_id": "abc123",
|
|
2341
|
+
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
|
|
2342
|
+
"cwd": "/Users/my-project",
|
|
2343
|
+
"hook_event_name": "DirectoryAdded",
|
|
2344
|
+
"directory": "/Users/my-other-repo",
|
|
2345
|
+
"source": "slash_command"
|
|
2346
|
+
}
|
|
2347
|
+
```
|
|
2348
|
+
|
|
2349
|
+
DirectoryAdded hooks have no decision control. They canโt block the add, which has already completed when the hook runs. Claude Code surfaces hook output differently per source:
|
|
2350
|
+
|
|
2351
|
+
- `slash_command`: unlike on every other event, where you see the `systemMessage` and Claude doesnโt, Claude Code delivers the hookโs `systemMessage` to Claude as context on the next conversation turn. A count of failed hooks appears in the transcript; full failure output goes to the debug log
|
|
2352
|
+
- `register_repo_root`: Claude Code writes `systemMessage` output and failure output to the debug log only
|
|
2353
|
+
|
|
2198
2354
|
### [โ](https://code.claude.com/docs/en/hooks\#filechanged) FileChanged
|
|
2199
2355
|
|
|
2200
2356
|
Runs when a watched file changes on disk. Useful for reloading environment variables when project configuration files are modified.The `matcher` for this event serves two roles:
|
|
@@ -2236,7 +2392,7 @@ FileChanged hooks have no decision control. They canโt block the file change f
|
|
|
2236
2392
|
|
|
2237
2393
|
### [โ](https://code.claude.com/docs/en/hooks\#worktreecreate) WorktreeCreate
|
|
2238
2394
|
|
|
2239
|
-
Runs when a worktree is being created,
|
|
2395
|
+
Runs when a worktree is being created, whether from `claude --worktree`, from a [subagent using `isolation: "worktree"`](https://code.claude.com/docs/en/sub-agents#choose-the-subagent-scope), or for a [background session](https://code.claude.com/docs/en/agent-view#how-file-edits-are-isolated) that Claude Code isolates in its own worktree. 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.This example creates an SVN working copy and prints the path for Claude Code to use. Replace the repository URL with your own:
|
|
2240
2396
|
|
|
2241
2397
|
```
|
|
2242
2398
|
{
|
|
@@ -2278,11 +2434,17 @@ WorktreeCreate hooks donโt use the standard allow/block decision model. Instea
|
|
|
2278
2434
|
- **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
2435
|
- **HTTP hooks** (`type: "http"`): return `{ "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } }` in the response body.
|
|
2280
2436
|
|
|
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. Before v2.1.205, a relative path or a path that didnโt exist on disk crashed the session at startup, and with `-p` it stalled for about 30 seconds before exiting with code 0.
|
|
2437
|
+
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. Before v2.1.205, a relative path or a path that didnโt exist on disk crashed the session at startup, and with `-p` it stalled for about 30 seconds before exiting with code 0.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
2438
|
|
|
2283
2439
|
### [โ](https://code.claude.com/docs/en/hooks\#worktreeremove) WorktreeRemove
|
|
2284
2440
|
|
|
2285
|
-
Runs when a worktree is being removed
|
|
2441
|
+
Runs when a worktree is being removed. This is the cleanup counterpart to [WorktreeCreate](https://code.claude.com/docs/en/hooks#worktreecreate). The event fires when:
|
|
2442
|
+
|
|
2443
|
+
- you exit a `--worktree` session and choose to remove it
|
|
2444
|
+
- a subagent with `isolation: "worktree"` finishes
|
|
2445
|
+
- you delete a [background session](https://code.claude.com/docs/en/agent-view#what-deleting-a-session-removes) whose worktree the hook created
|
|
2446
|
+
|
|
2447
|
+
For git-based worktrees, Claude Code handles cleanup automatically with `git worktree remove`. If you configured a WorktreeCreate hook for a non-git version control system, pair it with a WorktreeRemove hook to handle cleanup. Without one, the worktree directory is left on disk.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. 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
2448
|
|
|
2287
2449
|
```
|
|
2288
2450
|
{
|
|
@@ -2535,6 +2697,7 @@ Events that support `command`, `http`, and `mcp_tool` hooks but not `prompt` or
|
|
|
2535
2697
|
|
|
2536
2698
|
- `ConfigChange`
|
|
2537
2699
|
- `CwdChanged`
|
|
2700
|
+
- `DirectoryAdded`
|
|
2538
2701
|
- `Elicitation`
|
|
2539
2702
|
- `ElicitationResult`
|
|
2540
2703
|
- `FileChanged`
|
|
@@ -2560,7 +2723,7 @@ Instead of executing a Bash command, prompt-based hooks:
|
|
|
2560
2723
|
|
|
2561
2724
|
### [โ](https://code.claude.com/docs/en/hooks\#prompt-hook-configuration) Prompt hook configuration
|
|
2562
2725
|
|
|
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.
|
|
2726
|
+
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.This `Stop` hook asks the LLM to evaluate whether all tasks are complete before allowing Claude to finish:
|
|
2564
2727
|
|
|
2565
2728
|
```
|
|
2566
2729
|
{
|
|
@@ -2606,10 +2769,11 @@ The LLM must respond with JSON containing:
|
|
|
2606
2769
|
What happens on `ok: false` depends on the event:
|
|
2607
2770
|
|
|
2608
2771
|
- `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
|
|
2772
|
+
- `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
2773
|
- `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
2774
|
- `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
|
|
2775
|
+
- `PostToolUseFailure` and `TaskCreated`: the reason is returned to Claude as a tool error and the turn continues, regardless of `continueOnBlock`
|
|
2776
|
+
- `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
2777
|
- `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
2778
|
- `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
2779
|
- `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`
|
|
@@ -2714,7 +2878,10 @@ Add `"async": true` to a command hookโs configuration to run it in the backgro
|
|
|
2714
2878
|
}
|
|
2715
2879
|
```
|
|
2716
2880
|
|
|
2717
|
-
The `timeout` field sets the maximum time in seconds for the background process. If not specified, async hooks use the same 10-minute default as sync hooks.
|
|
2881
|
+
The `timeout` field sets the maximum time in seconds for the background process. If not specified, async hooks use the same 10-minute default as sync hooks.You receive an async hookโs results only while the session runs:
|
|
2882
|
+
|
|
2883
|
+
- 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`
|
|
2884
|
+
- If your hookโs work must outlive a `claude -p` session, start a fully detached process from it
|
|
2718
2885
|
|
|
2719
2886
|
### [โ](https://code.claude.com/docs/en/hooks\#how-async-hooks-execute) How async hooks execute
|
|
2720
2887
|
|
|
@@ -2774,10 +2941,8 @@ Then add this configuration to `.claude/settings.json` in your project root. The
|
|
|
2774
2941
|
|
|
2775
2942
|
### [โ](https://code.claude.com/docs/en/hooks\#limitations) Limitations
|
|
2776
2943
|
|
|
2777
|
-
Async hooks have
|
|
2944
|
+
Async hooks have additional constraints compared to synchronous hooks:
|
|
2778
2945
|
|
|
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
2946
|
- 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
2947
|
- Each execution creates a separate background process. There is no deduplication across multiple firings of the same async hook.
|
|
2783
2948
|
|
|
@@ -2785,8 +2950,6 @@ Async hooks have several constraints compared to synchronous hooks:
|
|
|
2785
2950
|
|
|
2786
2951
|
### [โ](https://code.claude.com/docs/en/hooks\#disclaimer) Disclaimer
|
|
2787
2952
|
|
|
2788
|
-
Command hooks run with your system userโs full permissions.
|
|
2789
|
-
|
|
2790
2953
|
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
2954
|
|
|
2792
2955
|
### [โ](https://code.claude.com/docs/en/hooks\#security-best-practices) Security best practices
|
|
@@ -2801,7 +2964,7 @@ Keep these practices in mind when writing hooks:
|
|
|
2801
2964
|
|
|
2802
2965
|
## [โ](https://code.claude.com/docs/en/hooks\#windows-powershell-tool) Windows PowerShell tool
|
|
2803
2966
|
|
|
2804
|
-
On Windows, you can run individual hooks in PowerShell by setting `"shell": "powershell"` on a command hook.
|
|
2967
|
+
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
2968
|
|
|
2806
2969
|
```
|
|
2807
2970
|
{
|
|
@@ -2834,13 +2997,12 @@ To reference the project root from a PowerShell shell-form command, write `${CLA
|
|
|
2834
2997
|
|
|
2835
2998
|
## [โ](https://code.claude.com/docs/en/hooks\#debug-hooks) Debug hooks
|
|
2836
2999
|
|
|
2837
|
-
Hook execution details, including which hooks matched, their exit codes, and full stdout and stderr, 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.
|
|
3000
|
+
Hook execution details, including which hooks matched, their exit codes, and full stdout and stderr, 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
3001
|
|
|
2839
3002
|
```
|
|
2840
|
-
[DEBUG]
|
|
2841
|
-
[DEBUG]
|
|
2842
|
-
|
|
2843
|
-
[DEBUG] Hook command completed with status 0: <Your stdout>
|
|
3003
|
+
2026-07-19T02:03:24.382Z [DEBUG] Hook output does not start with {, treating as plain text
|
|
3004
|
+
2026-07-19T02:03:24.382Z [DEBUG] Hook PostToolUse:Write (PostToolUse) success:
|
|
3005
|
+
hook-ran
|
|
2844
3006
|
```
|
|
2845
3007
|
|
|
2846
3008
|
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).
|
|
@@ -2849,14 +3011,16 @@ Was this page helpful?
|
|
|
2849
3011
|
|
|
2850
3012
|
YesNo
|
|
2851
3013
|
|
|
2852
|
-
[Checkpointing](https://code.claude.com/docs/en/checkpointing) [Plugins reference](https://code.claude.com/docs/en/plugins-reference)
|
|
2853
|
-
|
|
2854
3014
|
Ctrl+I
|
|
2855
3015
|
|
|
2856
3016
|
Assistant
|
|
2857
3017
|
|
|
2858
3018
|
Responses are generated using AI and may contain mistakes.
|
|
2859
3019
|
|
|
2860
|
-

|
|
3021
|
+
|
|
3022
|
+

|
|
3023
|
+
|
|
3024
|
+

|
|
2861
3025
|
|
|
2862
|
-
 match. If both match, the hook command runs and returns permissionDecision deny, so the tool call is blocked and Claude Code continues. If either check fails to match, the hook is skipped and the tool call is allowed to proceed.](https://mintcdn.com/claude-code/_xqph1dUOslCOwsj/images/hook-resolution-dark.svg?w=1100&fit=max&auto=format&n=_xqph1dUOslCOwsj&q=85&s=2f60e716dc01cc0f41783a1138f4ff72)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module ClaudeHooks
|
|
6
|
+
class DirectoryAdded < Base
|
|
7
|
+
def self.hook_type
|
|
8
|
+
'DirectoryAdded'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.input_fields
|
|
12
|
+
%w[directory source]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def directory
|
|
16
|
+
@input_data['directory']
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Values: slash_command | register_repo_root
|
|
20
|
+
def source
|
|
21
|
+
@input_data['source']
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module ClaudeHooks
|
|
6
|
+
module Output
|
|
7
|
+
# DirectoryAdded is non-blocking โ the directory is already added.
|
|
8
|
+
# Only `systemMessage` is consumed (as Claude context for `slash_command`,
|
|
9
|
+
# debug log for `register_repo_root`).
|
|
10
|
+
class DirectoryAdded < Base
|
|
11
|
+
def exit_code
|
|
12
|
+
0
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.merge(*outputs)
|
|
16
|
+
merged = super(*outputs)
|
|
17
|
+
new(merged.data)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/claude_hooks/version.rb
CHANGED
data/lib/claude_hooks.rb
CHANGED
|
@@ -37,6 +37,7 @@ require_relative "claude_hooks/permission_denied"
|
|
|
37
37
|
require_relative "claude_hooks/elicitation"
|
|
38
38
|
require_relative "claude_hooks/elicitation_result"
|
|
39
39
|
require_relative "claude_hooks/worktree_create"
|
|
40
|
+
require_relative "claude_hooks/directory_added"
|
|
40
41
|
|
|
41
42
|
# Output classes
|
|
42
43
|
require_relative "claude_hooks/output/base"
|
|
@@ -70,6 +71,7 @@ require_relative "claude_hooks/output/permission_denied"
|
|
|
70
71
|
require_relative "claude_hooks/output/elicitation"
|
|
71
72
|
require_relative "claude_hooks/output/elicitation_result"
|
|
72
73
|
require_relative "claude_hooks/output/worktree_create"
|
|
74
|
+
require_relative "claude_hooks/output/directory_added"
|
|
73
75
|
|
|
74
76
|
module ClaudeHooks
|
|
75
77
|
class Error < StandardError; end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: claude_hooks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gabriel Dehan
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|
|
@@ -73,6 +73,7 @@ files:
|
|
|
73
73
|
- docs/API/COMMON.md
|
|
74
74
|
- docs/API/CONFIG_CHANGE.md
|
|
75
75
|
- docs/API/CWD_CHANGED.md
|
|
76
|
+
- docs/API/DIRECTORY_ADDED.md
|
|
76
77
|
- docs/API/ELICITATION.md
|
|
77
78
|
- docs/API/ELICITATION_RESULT.md
|
|
78
79
|
- docs/API/FILE_CHANGED.md
|
|
@@ -124,6 +125,7 @@ files:
|
|
|
124
125
|
- lib/claude_hooks/config_change.rb
|
|
125
126
|
- lib/claude_hooks/configuration.rb
|
|
126
127
|
- lib/claude_hooks/cwd_changed.rb
|
|
128
|
+
- lib/claude_hooks/directory_added.rb
|
|
127
129
|
- lib/claude_hooks/elicitation.rb
|
|
128
130
|
- lib/claude_hooks/elicitation_result.rb
|
|
129
131
|
- lib/claude_hooks/file_changed.rb
|
|
@@ -134,6 +136,7 @@ files:
|
|
|
134
136
|
- lib/claude_hooks/output/base.rb
|
|
135
137
|
- lib/claude_hooks/output/config_change.rb
|
|
136
138
|
- lib/claude_hooks/output/cwd_changed.rb
|
|
139
|
+
- lib/claude_hooks/output/directory_added.rb
|
|
137
140
|
- lib/claude_hooks/output/elicitation.rb
|
|
138
141
|
- lib/claude_hooks/output/elicitation_result.rb
|
|
139
142
|
- lib/claude_hooks/output/file_changed.rb
|