1> ## Documentation Index
2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
3> Use this file to discover all available pages before exploring further.
4
1# Hooks reference5# Hooks reference
2 6
3> This page provides reference documentation for implementing hooks in Claude Code.7> Reference for Claude Code hook events, configuration schema, JSON input/output formats, exit codes, async hooks, prompt hooks, and MCP tool hooks.
4 8
5<Tip>9<Tip>
6 For a quickstart guide with examples, see [Get started with Claude Code hooks](/en/hooks-guide).10 For a quickstart guide with examples, see [Automate workflows with hooks](/en/hooks-guide).
7</Tip>11</Tip>
8 12
9## Configuration13Hooks are user-defined shell commands or LLM prompts that execute automatically at specific points in Claude Code's lifecycle. Use this reference to look up event schemas, configuration options, JSON input/output formats, and advanced features like async hooks and MCP tool hooks. If you're setting up hooks for the first time, start with the [guide](/en/hooks-guide) instead.
10 14
11Claude Code hooks are configured in your [settings files](/en/settings):15## Hook lifecycle
12 16
13* `~/.claude/settings.json` - User settings17Hooks fire at specific points during a Claude Code session. When an event fires and a matcher matches, Claude Code passes JSON context about the event to your hook handler. For command hooks, this arrives on stdin. Your handler can then inspect the input, take action, and optionally return a decision. Some events fire once per session, while others fire repeatedly inside the agentic loop:
14* `.claude/settings.json` - Project settings
15* `.claude/settings.local.json` - Local project settings (not committed)
16* Managed policy settings
17 18
18<Note>19<div style={{maxWidth: "500px", margin: "0 auto"}}>
19 Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. See [Hook configuration](/en/settings#hook-configuration).20 <Frame>
20</Note>21 <img src="https://mintcdn.com/claude-code/rsuu-ovdPNos9Dnn/images/hooks-lifecycle.svg?fit=max&auto=format&n=rsuu-ovdPNos9Dnn&q=85&s=ce5f1225339bbccdfbb52e99205db912" alt="Hook lifecycle diagram showing the sequence of hooks from SessionStart through the agentic loop to SessionEnd, with WorktreeCreate and WorktreeRemove as standalone setup and teardown events" data-og-width="520" width="520" data-og-height="1020" height="1020" data-path="images/hooks-lifecycle.svg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/claude-code/rsuu-ovdPNos9Dnn/images/hooks-lifecycle.svg?w=280&fit=max&auto=format&n=rsuu-ovdPNos9Dnn&q=85&s=7c7143c65492c1beb6bc66f5d206ba15 280w, https://mintcdn.com/claude-code/rsuu-ovdPNos9Dnn/images/hooks-lifecycle.svg?w=560&fit=max&auto=format&n=rsuu-ovdPNos9Dnn&q=85&s=dafaebf8f789f94edbf6bd66853c69df 560w, https://mintcdn.com/claude-code/rsuu-ovdPNos9Dnn/images/hooks-lifecycle.svg?w=840&fit=max&auto=format&n=rsuu-ovdPNos9Dnn&q=85&s=2caa51d2d95596f1f80b92e3f5f534fa 840w, https://mintcdn.com/claude-code/rsuu-ovdPNos9Dnn/images/hooks-lifecycle.svg?w=1100&fit=max&auto=format&n=rsuu-ovdPNos9Dnn&q=85&s=614def559f34f9b0c1dec93739d96b64 1100w, https://mintcdn.com/claude-code/rsuu-ovdPNos9Dnn/images/hooks-lifecycle.svg?w=1650&fit=max&auto=format&n=rsuu-ovdPNos9Dnn&q=85&s=ca45b85fdd8b2da81c69d12c453230cb 1650w, https://mintcdn.com/claude-code/rsuu-ovdPNos9Dnn/images/hooks-lifecycle.svg?w=2500&fit=max&auto=format&n=rsuu-ovdPNos9Dnn&q=85&s=7fd92d6b9713493f59962c9f295c9d2f 2500w" />
22 </Frame>
23</div>
21 24
22### Structure25The table below summarizes when each event fires. The [Hook events](#hook-events) section documents the full input schema and decision control options for each one.
23 26
24Hooks are organized by matchers, where each matcher can have multiple hooks:27| Event | When it fires |
28| :------------------- | :---------------------------------------------------------------------------------------------------------- |
29| `SessionStart` | When a session begins or resumes |
30| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |
31| `PreToolUse` | Before a tool call executes. Can block it |
32| `PermissionRequest` | When a permission dialog appears |
33| `PostToolUse` | After a tool call succeeds |
34| `PostToolUseFailure` | After a tool call fails |
35| `Notification` | When Claude Code sends a notification |
36| `SubagentStart` | When a subagent is spawned |
37| `SubagentStop` | When a subagent finishes |
38| `Stop` | When Claude finishes responding |
39| `TeammateIdle` | When an [agent team](/en/agent-teams) teammate is about to go idle |
40| `TaskCompleted` | When a task is being marked as completed |
41| `ConfigChange` | When a configuration file changes during a session |
42| `WorktreeCreate` | When a worktree is being created via `--worktree` or `isolation: "worktree"`. Replaces default git behavior |
43| `WorktreeRemove` | When a worktree is being removed, either at session exit or when a subagent finishes |
44| `PreCompact` | Before context compaction |
45| `SessionEnd` | When a session terminates |
25 46
26```json theme={null}47### How a hook resolves
27{
28 "hooks": {
29 "EventName": [
30 {
31 "matcher": "ToolPattern",
32 "hooks": [
33 {
34 "type": "command",
35 "command": "your-command-here"
36 }
37 ]
38 }
39 ]
40 }
41}
42```
43 48
44* **matcher**: Pattern to match tool names, case-sensitive (only applicable for49To see how these pieces fit together, consider this `PreToolUse` hook that blocks destructive shell commands. The hook runs `block-rm.sh` before every Bash tool call:
45 `PreToolUse`, `PermissionRequest`, and `PostToolUse`)
46 * Simple strings match exactly: `Write` matches only the Write tool
47 * Supports regex: `Edit|Write` or `Notebook.*`
48 * Use `*` to match all tools. You can also use empty string (`""`) or leave
49 `matcher` blank.
50* **hooks**: Array of hooks to execute when the pattern matches
51 * `type`: Hook execution type - `"command"` for bash commands or `"prompt"` for LLM-based evaluation
52 * `command`: (For `type: "command"`) The bash command to execute (can use `$CLAUDE_PROJECT_DIR` environment variable)
53 * `prompt`: (For `type: "prompt"`) The prompt to send to the LLM for evaluation
54 * `timeout`: (Optional) How long a hook should run, in seconds, before canceling that specific hook
55
56For events like `UserPromptSubmit`, `Stop`, and `SubagentStop`
57that don't use matchers, you can omit the matcher field:
58 50
59```json theme={null}51```json theme={null}
60{52{
61 "hooks": {53 "hooks": {
62 "UserPromptSubmit": [54 "PreToolUse": [
63 {55 {
56 "matcher": "Bash",
64 "hooks": [57 "hooks": [
65 {58 {
66 "type": "command",59 "type": "command",
67 "command": "/path/to/prompt-validator.py"60 "command": ".claude/hooks/block-rm.sh"
68 }61 }
69 ]62 ]
70 }63 }
73}66}
74```67```
75 68
76### Project-Specific Hook Scripts69The script reads the JSON input from stdin, extracts the command, and returns a `permissionDecision` of `"deny"` if it contains `rm -rf`:
77
78You can use the environment variable `CLAUDE_PROJECT_DIR` (only available when
79Claude Code spawns the hook command) to reference scripts stored in your project,
80ensuring they work regardless of Claude's current directory:
81 70
82```json theme={null}71```bash theme={null}
83{72#!/bin/bash
84 "hooks": {73# .claude/hooks/block-rm.sh
85 "PostToolUse": [74COMMAND=$(jq -r '.tool_input.command')
86 {75
87 "matcher": "Write|Edit",76if echo "$COMMAND" | grep -q 'rm -rf'; then
88 "hooks": [77 jq -n '{
89 {78 hookSpecificOutput: {
90 "type": "command",79 hookEventName: "PreToolUse",
91 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"80 permissionDecision: "deny",
92 }81 permissionDecisionReason: "Destructive command blocked by hook"
93 ]
94 }
95 ]
96 }82 }
97}83 }'
84else
85 exit 0 # allow the command
86fi
98```87```
99 88
100### Plugin hooks89Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:
101 90
102[Plugins](/en/plugins) can provide hooks that integrate seamlessly with your user and project hooks. Plugin hooks are automatically merged with your configuration when plugins are enabled.91<Frame>
92 <img src="https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=5bb890134390ecd0581477cf41ef730b" alt="Hook resolution flow: PreToolUse event fires, matcher checks for Bash match, hook handler runs, result returns to Claude Code" data-og-width="780" width="780" data-og-height="290" height="290" data-path="images/hook-resolution.svg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=280&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=5dcaecd24c260b8a90365d74e2c1fcda 280w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=560&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=c03d91c279f01d92e58ddd70fdbe66f2 560w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=840&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=1be57a4819cbb949a5ea9d08a05c9ecd 840w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=1100&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=0e9dd1807dc7a5c56011d0889b0d5208 1100w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=1650&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=69496ac02e70fabfece087ba31a1dcfc 1650w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=2500&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=a012346cb46a33b86580348802055267 2500w" />
93</Frame>
103 94
104**How plugin hooks work**:95<Steps>
96 <Step title="Event fires">
97 The `PreToolUse` event fires. Claude Code sends the tool input as JSON on stdin to the hook:
105 98
106* Plugin hooks are defined in the plugin's `hooks/hooks.json` file or in a file given by a custom path to the `hooks` field.99 ```json theme={null}
107* When a plugin is enabled, its hooks are merged with user and project hooks100 { "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
108* Multiple hooks from different sources can respond to the same event101 ```
109* Plugin hooks use the `${CLAUDE_PLUGIN_ROOT}` environment variable to reference plugin files102 </Step>
110 103
111**Example plugin hook configuration**:104 <Step title="Matcher checks">
105 The matcher `"Bash"` matches the tool name, so `block-rm.sh` runs. If you omit the matcher or use `"*"`, the hook runs on every occurrence of the event. Hooks only skip when a matcher is defined and doesn't match.
106 </Step>
112 107
113```json theme={null}108 <Step title="Hook handler runs">
114{109 The script extracts `"rm -rf /tmp/build"` from the input and finds `rm -rf`, so it prints a decision to stdout:
115 "description": "Automatic code formatting",110
116 "hooks": {111 ```json theme={null}
117 "PostToolUse": [
118 {
119 "matcher": "Write|Edit",
120 "hooks": [
121 {112 {
122 "type": "command",113 "hookSpecificOutput": {
123 "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",114 "hookEventName": "PreToolUse",
124 "timeout": 30115 "permissionDecision": "deny",
125 }116 "permissionDecisionReason": "Destructive command blocked by hook"
126 ]
127 }117 }
128 ]
129 }118 }
130}119 ```
131```
132
133<Note>
134 Plugin hooks use the same format as regular hooks with an optional `description` field to explain the hook's purpose.
135</Note>
136
137<Note>
138 Plugin hooks run alongside your custom hooks. If multiple hooks match an event, they all execute in parallel.
139</Note>
140
141**Environment variables for plugins**:
142 120
143* `${CLAUDE_PLUGIN_ROOT}`: Absolute path to the plugin directory121 If the command had been safe (like `npm test`), the script would hit `exit 0` instead, which tells Claude Code to allow the tool call with no further action.
144* `${CLAUDE_PROJECT_DIR}`: Project root directory (same as for project hooks)122 </Step>
145* All standard environment variables are available
146 123
147See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.124 <Step title="Claude Code acts on the result">
125 Claude Code reads the JSON decision, blocks the tool call, and shows Claude the reason.
126 </Step>
127</Steps>
148 128
149### Hooks in skills and agents129The [Configuration](#configuration) section below documents the full schema, and each [hook event](#hook-events) section documents what input your command receives and what output it can return.
150
151In addition to settings files and plugins, hooks can be defined directly in [skills](/en/skills) and [subagents](/en/sub-agents) using frontmatter. These hooks are scoped to the component's lifecycle and only run when that component is active.
152 130
153**Supported events**: `PreToolUse`, `PostToolUse`, and `Stop`131## Configuration
154 132
155**Example in a Skill**:133Hooks are defined in JSON settings files. The configuration has three levels of nesting:
156 134
157```yaml theme={null}1351. Choose a [hook event](#hook-events) to respond to, like `PreToolUse` or `Stop`
158name: secure-operations1362. Add a [matcher group](#matcher-patterns) to filter when it fires, like "only for the Bash tool"
159description: Perform operations with security checks1373. Define one or more [hook handlers](#hook-handler-fields) to run when matched
160hooks:
161 PreToolUse:
162 - matcher: "Bash"
163 hooks:
164 - type: command
165 command: "./scripts/security-check.sh"
166```
167 138
168**Example in an agent**:139See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.
169 140
170```yaml theme={null}141<Note>
171name: code-reviewer142 This page uses specific terms for each level: **hook event** for the lifecycle point, **matcher group** for the filter, and **hook handler** for the shell command, prompt, or agent that runs. "Hook" on its own refers to the general feature.
172description: Review code changes143</Note>
173hooks:
174 PostToolUse:
175 - matcher: "Edit|Write"
176 hooks:
177 - type: command
178 command: "./scripts/run-linter.sh"
179```
180 144
181Component-scoped hooks follow the same configuration format as settings-based hooks but are automatically cleaned up when the component finishes executing.145### Hook locations
182 146
183**Additional option for skills:**147Where you define a hook determines its scope:
184 148
185* `once`: Set to `true` to run the hook only once per session. After the first successful execution, the hook is removed. Note: This option is currently only supported for skills, not for agents.149| Location | Scope | Shareable |
150| :--------------------------------------------------------- | :---------------------------- | :--------------------------------- |
151| `~/.claude/settings.json` | All your projects | No, local to your machine |
152| `.claude/settings.json` | Single project | Yes, can be committed to the repo |
153| `.claude/settings.local.json` | Single project | No, gitignored |
154| Managed policy settings | Organization-wide | Yes, admin-controlled |
155| [Plugin](/en/plugins) `hooks/hooks.json` | When plugin is enabled | Yes, bundled with the plugin |
156| [Skill](/en/skills) or [agent](/en/sub-agents) frontmatter | While the component is active | Yes, defined in the component file |
186 157
187## Prompt-Based Hooks158For details on settings file resolution, see [settings](/en/settings). Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. See [Hook configuration](/en/settings#hook-configuration).
188 159
189In addition to bash command hooks (`type: "command"`), Claude Code supports prompt-based hooks (`type: "prompt"`) that use an LLM to evaluate whether to allow or block an action. Prompt-based hooks are currently only supported for `Stop` and `SubagentStop` hooks, where they enable intelligent, context-aware decisions.160### Matcher patterns
190 161
191### How prompt-based hooks work162The `matcher` field is a regex string that filters when hooks fire. Use `"*"`, `""`, or omit `matcher` entirely to match all occurrences. Each event type matches on a different field:
192 163
193Instead of executing a bash command, prompt-based hooks:164| Event | What the matcher filters | Example matcher values |
165| :---------------------------------------------------------------------------------------------- | :------------------------ | :--------------------------------------------------------------------------------- |
166| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |
167| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |
168| `SessionEnd` | why the session ended | `clear`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |
169| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |
170| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |
171| `PreCompact` | what triggered compaction | `manual`, `auto` |
172| `SubagentStop` | agent type | same values as `SubagentStart` |
173| `ConfigChange` | configuration source | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` |
174| `UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove` | no matcher support | always fires on every occurrence |
194 175
1951. Send the hook input and your prompt to a fast LLM (Haiku)176The matcher is a regex, so `Edit|Write` matches either tool and `Notebook.*` matches any tool starting with Notebook. The matcher runs against a field from the [JSON input](#hook-input-and-output) that Claude Code sends to your hook on stdin. For tool events, that field is `tool_name`. Each [hook event](#hook-events) section lists the full set of matcher values and the input schema for that event.
1962. The LLM responds with structured JSON containing a decision
1973. Claude Code processes the decision automatically
198 177
199### Configuration178This example runs a linting script only when Claude writes or edits a file:
200 179
201```json theme={null}180```json theme={null}
202{181{
203 "hooks": {182 "hooks": {
204 "Stop": [183 "PostToolUse": [
205 {184 {
185 "matcher": "Edit|Write",
206 "hooks": [186 "hooks": [
207 {187 {
208 "type": "prompt",188 "type": "command",
209 "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."189 "command": "/path/to/lint-check.sh"
210 }190 }
211 ]191 ]
212 }192 }
219}195}
220```196```
221 197
222**Fields:**198`UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCompleted`, `WorktreeCreate`, and `WorktreeRemove` don't support matchers and always fire on every occurrence. If you add a `matcher` field to these events, it is silently ignored.
223
224* `type`: Must be `"prompt"`
225* `prompt`: The prompt text to send to the LLM
226 * Use `$ARGUMENTS` as a placeholder for the hook input JSON
227 * If `$ARGUMENTS` is not present, input JSON is appended to the prompt
228* `timeout`: (Optional) Timeout in seconds (default: 30 seconds)
229
230### Response schema
231
232The LLM must respond with JSON containing:
233 199
234```json theme={null}200#### Match MCP tools
235{
236 "ok": true | false,
237 "reason": "Explanation for the decision"
238}
239```
240 201
241**Response fields:**202[MCP](/en/mcp) server tools appear as regular tools in tool events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`), so you can match them the same way you match any other tool name.
242 203
243* `ok`: `true` allows the action, `false` prevents it204MCP tools follow the naming pattern `mcp__<server>__<tool>`, for example:
244* `reason`: Required when `ok` is `false`. Explanation shown to Claude
245 205
246### Supported hook events206* `mcp__memory__create_entities`: Memory server's create entities tool
207* `mcp__filesystem__read_file`: Filesystem server's read file tool
208* `mcp__github__search_repositories`: GitHub server's search tool
247 209
248Prompt-based hooks work with any hook event, but are most useful for:210Use regex patterns to target specific MCP tools or groups of tools:
249 211
250* **Stop**: Intelligently decide if Claude should continue working212* `mcp__memory__.*` matches all tools from the `memory` server
251* **SubagentStop**: Evaluate if a subagent has completed its task213* `mcp__.*__write.*` matches any tool containing "write" from any server
252* **UserPromptSubmit**: Validate user prompts with LLM assistance
253* **PreToolUse**: Make context-aware permission decisions
254* **PermissionRequest**: Intelligently allow or deny permission dialogs
255 214
256### Example: Intelligent Stop hook215This example logs all memory server operations and validates write operations from any MCP server:
257 216
258```json theme={null}217```json theme={null}
259{218{
260 "hooks": {219 "hooks": {
261 "Stop": [220 "PreToolUse": [
262 {221 {
222 "matcher": "mcp__memory__.*",
263 "hooks": [223 "hooks": [
264 {224 {
265 "type": "prompt",225 "type": "command",
266 "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",226 "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
267 "timeout": 30
268 }
269 ]
270 }227 }
271 ]228 ]
272 }229 },
273}
274```
275
276### Example: SubagentStop with custom logic
277
278```json theme={null}
279{
280 "hooks": {
281 "SubagentStop": [
282 {230 {
231 "matcher": "mcp__.*__write.*",
283 "hooks": [232 "hooks": [
284 {233 {
285 "type": "prompt",234 "type": "command",
286 "prompt": "Evaluate if this subagent should stop. Input: $ARGUMENTS\n\nCheck if:\n- The subagent completed its assigned task\n- Any errors occurred that need fixing\n- Additional context gathering is needed\n\nReturn: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"explanation\"} to continue."235 "command": "/home/user/scripts/validate-mcp-write.py"
287 }236 }
288 ]237 ]
289 }238 }
292}241}
293```242```
294 243
295### Comparison with bash command hooks244### Hook handler fields
296
297| Feature | Bash Command Hooks | Prompt-Based Hooks |
298| --------------------- | ----------------------- | ------------------------------ |
299| **Execution** | Runs bash script | Queries LLM |
300| **Decision logic** | You implement in code | LLM evaluates context |
301| **Setup complexity** | Requires script file | Configure prompt |
302| **Context awareness** | Limited to script logic | Natural language understanding |
303| **Performance** | Fast (local execution) | Slower (API call) |
304| **Use case** | Deterministic rules | Context-aware decisions |
305
306### Best practices
307
308* **Be specific in prompts**: Clearly state what you want the LLM to evaluate
309* **Include decision criteria**: List the factors the LLM should consider
310* **Test your prompts**: Verify the LLM makes correct decisions for your use cases
311* **Set appropriate timeouts**: Default is 30 seconds, adjust if needed
312* **Use for complex decisions**: Bash hooks are better for simple, deterministic rules
313
314See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.
315
316## Hook Events
317 245
318### PreToolUse246Each object in the inner `hooks` array is a hook handler: the shell command, LLM prompt, or agent that runs when the matcher matches. There are three types:
319
320Runs after Claude creates tool parameters and before processing the tool call.
321 247
322**Common matchers:**248* **[Command hooks](#command-hook-fields)** (`type: "command"`): run a shell command. Your script receives the event's [JSON input](#hook-input-and-output) on stdin and communicates results back through exit codes and stdout.
249* **[Prompt hooks](#prompt-and-agent-hook-fields)** (`type: "prompt"`): send a prompt to a Claude model for single-turn evaluation. The model returns a yes/no decision as JSON. See [Prompt-based hooks](#prompt-based-hooks).
250* **[Agent hooks](#prompt-and-agent-hook-fields)** (`type: "agent"`): spawn a subagent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. See [Agent-based hooks](#agent-based-hooks).
323 251
324* `Task` - Subagent tasks (see [subagents documentation](/en/sub-agents))252#### Common fields
325* `Bash` - Shell commands
326* `Glob` - File pattern matching
327* `Grep` - Content search
328* `Read` - File reading
329* `Edit` - File editing
330* `Write` - File writing
331* `WebFetch`, `WebSearch` - Web operations
332 253
333Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.254These fields apply to all hook types:
334 255
335### PermissionRequest256| Field | Required | Description |
257| :-------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |
258| `type` | yes | `"command"`, `"prompt"`, or `"agent"` |
259| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |
260| `statusMessage` | no | Custom spinner message displayed while the hook runs |
261| `once` | no | If `true`, runs only once per session then is removed. Skills only, not agents. See [Hooks in skills and agents](#hooks-in-skills-and-agents) |
336 262
337Runs when the user is shown a permission dialog.263#### Command hook fields
338Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.
339 264
340Recognizes the same matcher values as PreToolUse.265In addition to the [common fields](#common-fields), command hooks accept these fields:
341 266
342### PostToolUse267| Field | Required | Description |
268| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------ |
269| `command` | yes | Shell command to execute |
270| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |
343 271
344Runs immediately after a tool completes successfully.272#### Prompt and agent hook fields
345 273
346Recognizes the same matcher values as PreToolUse.274In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:
347 275
348### Notification276| Field | Required | Description |
277| :------- | :------- | :------------------------------------------------------------------------------------------ |
278| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
279| `model` | no | Model to use for evaluation. Defaults to a fast model |
349 280
350Runs when Claude Code sends notifications. Supports matchers to filter by notification type.281All matching hooks run in parallel, and identical handlers are deduplicated automatically. Handlers run in the current directory with Claude Code's environment. The `$CLAUDE_CODE_REMOTE` environment variable is set to `"true"` in remote web environments and not set in the local CLI.
351 282
352**Common matchers:**283### Reference scripts by path
353 284
354* `permission_prompt` - Permission requests from Claude Code285Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:
355* `idle_prompt` - When Claude is waiting for user input (after 60+ seconds of idle time)
356* `auth_success` - Authentication success notifications
357* `elicitation_dialog` - When Claude Code needs input for MCP tool elicitation
358 286
359You can use matchers to run different hooks for different notification types, or omit the matcher to run hooks for all notifications.287* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.
288* `${CLAUDE_PLUGIN_ROOT}`: the plugin's root directory, for scripts bundled with a [plugin](/en/plugins).
360 289
361**Example: Different notifications for different types**290<Tabs>
291 <Tab title="Project scripts">
292 This example uses `$CLAUDE_PROJECT_DIR` to run a style checker from the project's `.claude/hooks/` directory after any `Write` or `Edit` tool call:
362 293
363```json theme={null}294 ```json theme={null}
364{295 {
365 "hooks": {296 "hooks": {
366 "Notification": [297 "PostToolUse": [
367 {298 {
368 "matcher": "permission_prompt",299 "matcher": "Write|Edit",
369 "hooks": [300 "hooks": [
370 {301 {
371 "type": "command",302 "type": "command",
372 "command": "/path/to/permission-alert.sh"303 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
373 }304 }
374 ]305 ]
375 },306 }
307 ]
308 }
309 }
310 ```
311 </Tab>
312
313 <Tab title="Plugin scripts">
314 Define plugin hooks in `hooks/hooks.json` with an optional top-level `description` field. When a plugin is enabled, its hooks merge with your user and project hooks.
315
316 This example runs a formatting script bundled with the plugin:
317
318 ```json theme={null}
376 {319 {
377 "matcher": "idle_prompt",320 "description": "Automatic code formatting",
321 "hooks": {
322 "PostToolUse": [
323 {
324 "matcher": "Write|Edit",
378 "hooks": [325 "hooks": [
379 {326 {
380 "type": "command",327 "type": "command",
381 "command": "/path/to/idle-notification.sh"328 "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
329 "timeout": 30
382 }330 }
383 ]331 ]
384 }332 }
385 ]333 ]
386 }334 }
387}335 }
388```336 ```
389 337
390### UserPromptSubmit338 See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.
339 </Tab>
340</Tabs>
391 341
392Runs when the user submits a prompt, before Claude processes it. This allows you342### Hooks in skills and agents
393to add additional context based on the prompt/conversation, validate prompts, or
394block certain types of prompts.
395 343
396### Stop344In addition to settings files and plugins, hooks can be defined directly in [skills](/en/skills) and [subagents](/en/sub-agents) using frontmatter. These hooks are scoped to the component's lifecycle and only run when that component is active.
397 345
398Runs when the main Claude Code agent has finished responding. Does not run if346All hook events are supported. For subagents, `Stop` hooks are automatically converted to `SubagentStop` since that is the event that fires when a subagent completes.
399the stoppage occurred due to a user interrupt.
400 347
401### SubagentStop348Hooks use the same configuration format as settings-based hooks but are scoped to the component's lifetime and cleaned up when it finishes.
402 349
403Runs when a Claude Code subagent (Task tool call) has finished responding.350This skill defines a `PreToolUse` hook that runs a security validation script before each `Bash` command:
404 351
405### PreCompact352```yaml theme={null}
353---
354name: secure-operations
355description: Perform operations with security checks
356hooks:
357 PreToolUse:
358 - matcher: "Bash"
359 hooks:
360 - type: command
361 command: "./scripts/security-check.sh"
362---
363```
406 364
407Runs before Claude Code is about to run a compact operation.365Agents use the same format in their YAML frontmatter.
408 366
409**Matchers:**367### The `/hooks` menu
410 368
411* `manual` - Invoked from `/compact`369Type `/hooks` in Claude Code to open the interactive hooks manager, where you can view, add, and delete hooks without editing settings files directly. For a step-by-step walkthrough, see [Set up your first hook](/en/hooks-guide#set-up-your-first-hook) in the guide.
412* `auto` - Invoked from auto-compact (due to full context window)
413 370
414### SessionStart371Each hook in the menu is labeled with a bracket prefix indicating its source:
415 372
416Runs when Claude Code starts a new session or resumes an existing session (which373* `[User]`: from `~/.claude/settings.json`
417currently does start a new session under the hood). Useful for loading in374* `[Project]`: from `.claude/settings.json`
418development context like existing issues or recent changes to your codebase, installing dependencies, or setting up environment variables.375* `[Local]`: from `.claude/settings.local.json`
376* `[Plugin]`: from a plugin's `hooks/hooks.json`, read-only
419 377
420**Matchers:**378### Disable or remove hooks
421 379
422* `startup` - Invoked from startup380To remove a hook, delete its entry from the settings JSON file, or use the `/hooks` menu and select the hook to delete it.
423* `resume` - Invoked from `--resume`, `--continue`, or `/resume`
424* `clear` - Invoked from `/clear`
425* `compact` - Invoked from auto or manual compact.
426 381
427#### Persisting environment variables382To temporarily disable all hooks without removing them, set `"disableAllHooks": true` in your settings file or use the toggle in the `/hooks` menu. There is no way to disable an individual hook while keeping it in the configuration.
428 383
429SessionStart 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.384The `disableAllHooks` setting respects the managed settings hierarchy. If an administrator has configured hooks through managed policy settings, `disableAllHooks` set in user, project, or local settings cannot disable those managed hooks. Only `disableAllHooks` set at the managed settings level can disable managed hooks.
430 385
431**Example: Setting individual environment variables**386Direct edits to hooks in settings files don't take effect immediately. Claude Code captures a snapshot of hooks at startup and uses it throughout the session. This prevents malicious or accidental hook modifications from taking effect mid-session without your review. If hooks are modified externally, Claude Code warns you and requires review in the `/hooks` menu before changes apply.
432 387
433```bash theme={null}388## Hook input and output
434#!/bin/bash
435 389
436if [ -n "$CLAUDE_ENV_FILE" ]; then390Hooks receive JSON data via stdin and communicate results through exit codes, stdout, and stderr. This section covers fields and behavior common to all events. Each event's section under [Hook events](#hook-events) includes its specific input schema and decision control options.
437 echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
438 echo 'export API_KEY=your-api-key' >> "$CLAUDE_ENV_FILE"
439 echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
440fi
441 391
442exit 0392### Common input fields
393
394All hook events receive these fields via stdin as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section:
395
396| Field | Description |
397| :---------------- | :----------------------------------------------------------------------------------------------------------------------------------------- |
398| `session_id` | Current session identifier |
399| `transcript_path` | Path to conversation JSON |
400| `cwd` | Current working directory when the hook is invoked |
401| `permission_mode` | Current [permission mode](/en/permissions#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"dontAsk"`, or `"bypassPermissions"` |
402| `hook_event_name` | Name of the event that fired |
403
404For example, a `PreToolUse` hook for a Bash command receives this on stdin:
405
406```json theme={null}
407{
408 "session_id": "abc123",
409 "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
410 "cwd": "/home/user/my-project",
411 "permission_mode": "default",
412 "hook_event_name": "PreToolUse",
413 "tool_name": "Bash",
414 "tool_input": {
415 "command": "npm test"
416 }
417}
443```418```
444 419
445**Example: Persisting all environment changes from the hook**420The `tool_name` and `tool_input` fields are event-specific. Each [hook event](#hook-events) section documents the additional fields for that event.
446 421
447When your setup modifies the environment (for example, `nvm use`), capture and persist all changes by diffing the environment:422### Exit code output
448 423
449```bash theme={null}424The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.
450#!/bin/bash
451 425
452ENV_BEFORE=$(export -p | sort)426**Exit 0** means success. Claude Code parses stdout for [JSON output fields](#json-output). JSON output is only processed on exit 0. For most events, stdout is only shown in verbose mode (`Ctrl+O`). The exceptions are `UserPromptSubmit` and `SessionStart`, where stdout is added as context that Claude can see and act on.
453 427
454# Run your setup commands that modify the environment428**Exit 2** means a blocking error. Claude Code ignores stdout and any JSON in it. Instead, stderr text is fed back to Claude as an error message. The effect depends on the event: `PreToolUse` blocks the tool call, `UserPromptSubmit` rejects the prompt, and so on. See [exit code 2 behavior](#exit-code-2-behavior-per-event) for the full list.
455source ~/.nvm/nvm.sh
456nvm use 20
457 429
458if [ -n "$CLAUDE_ENV_FILE" ]; then430**Any other exit code** is a non-blocking error. stderr is shown in verbose mode (`Ctrl+O`) and execution continues.
459 ENV_AFTER=$(export -p | sort)431
460 comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"432For example, a hook command script that blocks dangerous Bash commands:
433
434```bash theme={null}
435#!/bin/bash
436# Reads JSON input from stdin, checks the command
437command=$(jq -r '.tool_input.command' < /dev/stdin)
438
439if [[ "$command" == rm* ]]; then
440 echo "Blocked: rm commands are not allowed" >&2
441 exit 2 # Blocking error: tool call is prevented
461fi442fi
462 443
463exit 0444exit 0 # Success: tool call proceeds
464```445```
465 446
466Any variables written to this file will be available in all subsequent bash commands that Claude Code executes during the session.447#### Exit code 2 behavior per event
448
449Exit code 2 is the way a hook signals "stop, don't do this." The effect depends on the event, because some events represent actions that can be blocked (like a tool call that hasn't happened yet) and others represent things that already happened or can't be prevented.
450
451| Hook event | Can block? | What happens on exit 2 |
452| :------------------- | :--------- | :---------------------------------------------------------------------------- |
453| `PreToolUse` | Yes | Blocks the tool call |
454| `PermissionRequest` | Yes | Denies the permission |
455| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |
456| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |
457| `SubagentStop` | Yes | Prevents the subagent from stopping |
458| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |
459| `TaskCompleted` | Yes | Prevents the task from being marked as completed |
460| `ConfigChange` | Yes | Blocks the configuration change from taking effect (except `policy_settings`) |
461| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |
462| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |
463| `Notification` | No | Shows stderr to user only |
464| `SubagentStart` | No | Shows stderr to user only |
465| `SessionStart` | No | Shows stderr to user only |
466| `SessionEnd` | No | Shows stderr to user only |
467| `PreCompact` | No | Shows stderr to user only |
468| `WorktreeCreate` | Yes | Any non-zero exit code causes worktree creation to fail |
469| `WorktreeRemove` | No | Failures are logged in debug mode only |
470
471### JSON output
472
473Exit codes let you allow or block, but JSON output gives you finer-grained control. Instead of exiting with code 2 to block, exit 0 and print a JSON object to stdout. Claude Code reads specific fields from that JSON to control behavior, including [decision control](#decision-control) for blocking, allowing, or escalating to the user.
467 474
468<Note>475<Note>
469 `CLAUDE_ENV_FILE` is only available for SessionStart hooks. Other hook types do not have access to this variable.476 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.
470</Note>477</Note>
471 478
472### SessionEnd479Your hook's stdout must contain only the JSON object. If your shell profile prints text on startup, it can interfere with JSON parsing. See [JSON validation failed](/en/hooks-guide#json-validation-failed) in the troubleshooting guide.
473
474Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
475statistics, or saving session state.
476 480
477The `reason` field in the hook input will be one of:481The JSON object supports three kinds of fields:
478 482
479* `clear` - Session cleared with /clear command483* **Universal fields** like `continue` work across all events. These are listed in the table below.
480* `logout` - User logged out484* **Top-level `decision` and `reason`** are used by some events to block or provide feedback.
481* `prompt_input_exit` - User exited while prompt input was visible485* **`hookSpecificOutput`** is a nested object for events that need richer control. It requires a `hookEventName` field set to the event name.
482* `other` - Other exit reasons
483 486
484## Hook Input487| Field | Default | Description |
488| :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------- |
489| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over any event-specific decision fields |
490| `stopReason` | none | Message shown to the user when `continue` is `false`. Not shown to Claude |
491| `suppressOutput` | `false` | If `true`, hides stdout from verbose mode output |
492| `systemMessage` | none | Warning message shown to the user |
485 493
486Hooks receive JSON data via stdin containing session information and494To stop Claude entirely regardless of event type:
487event-specific data:
488 495
489```typescript theme={null}496```json theme={null}
490{497{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
491 // Common fields
492 session_id: string
493 transcript_path: string // Path to conversation JSON
494 cwd: string // The current working directory when the hook is invoked
495 permission_mode: string // Current permission mode: "default", "plan", "acceptEdits", "dontAsk", or "bypassPermissions"
496
497 // Event-specific fields
498 hook_event_name: string
499 ...
500}
501```498```
502 499
503### PreToolUse Input500#### Decision control
504 501
505The exact schema for `tool_input` depends on the tool. Here are examples for commonly hooked tools.502Not every event supports blocking or controlling behavior through JSON. The events that do each use a different set of fields to express that decision. Use this table as a quick reference before writing a hook:
506 503
507#### Bash tool504| Events | Decision pattern | Key fields |
505| :---------------------------------------------------------------------------------- | :------------------- | :-------------------------------------------------------------------------- |
506| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange | Top-level `decision` | `decision: "block"`, `reason` |
507| TeammateIdle, TaskCompleted | Exit code only | Exit code 2 blocks the action, stderr is fed back as feedback |
508| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask), `permissionDecisionReason` |
509| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |
510| WorktreeCreate | stdout path | Hook prints absolute path to created worktree. Non-zero exit fails creation |
511| WorktreeRemove, Notification, SessionEnd, PreCompact | None | No decision control. Used for side effects like logging or cleanup |
508 512
509The Bash tool is the most commonly hooked tool for command validation:513Here are examples of each pattern in action:
510 514
511```json theme={null}515<Tabs>
512{516 <Tab title="Top-level decision">
513 "session_id": "abc123",517 Used by `UserPromptSubmit`, `PostToolUse`, `PostToolUseFailure`, `Stop`, `SubagentStop`, and `ConfigChange`. The only value is `"block"`. To allow the action to proceed, omit `decision` from your JSON, or exit 0 without any JSON at all:
514 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
515 "cwd": "/Users/...",
516 "permission_mode": "default",
517 "hook_event_name": "PreToolUse",
518 "tool_name": "Bash",
519 "tool_input": {
520 "command": "psql -c 'SELECT * FROM users'",
521 "description": "Query the users table",
522 "timeout": 120000
523 },
524 "tool_use_id": "toolu_01ABC123..."
525}
526```
527 518
528| Field | Type | Description |519 ```json theme={null}
529| :------------------ | :------ | :-------------------------------------------- |520 {
530| `command` | string | The shell command to execute |521 "decision": "block",
531| `description` | string | Optional description of what the command does |522 "reason": "Test suite must pass before proceeding"
532| `timeout` | number | Optional timeout in milliseconds |523 }
533| `run_in_background` | boolean | Whether to run the command in background |524 ```
525 </Tab>
534 526
535#### Write tool527 <Tab title="PreToolUse">
528 Uses `hookSpecificOutput` for richer control: allow, deny, or escalate to the user. You can also modify tool input before it runs or inject additional context for Claude. See [PreToolUse decision control](#pretooluse-decision-control) for the full set of options.
529
530 ```json theme={null}
531 {
532 "hookSpecificOutput": {
533 "hookEventName": "PreToolUse",
534 "permissionDecision": "deny",
535 "permissionDecisionReason": "Database writes are not allowed"
536 }
537 }
538 ```
539 </Tab>
540
541 <Tab title="PermissionRequest">
542 Uses `hookSpecificOutput` to allow or deny a permission request on behalf of the user. When allowing, you can also modify the tool's input or apply permission rules so the user isn't prompted again. See [PermissionRequest decision control](#permissionrequest-decision-control) for the full set of options.
543
544 ```json theme={null}
545 {
546 "hookSpecificOutput": {
547 "hookEventName": "PermissionRequest",
548 "decision": {
549 "behavior": "allow",
550 "updatedInput": {
551 "command": "npm run lint"
552 }
553 }
554 }
555 }
556 ```
557 </Tab>
558</Tabs>
559
560For extended examples including Bash command validation, prompt filtering, and auto-approval scripts, see [What you can automate](/en/hooks-guide#what-you-can-automate) in the guide and the [Bash command validator reference implementation](https://github.com/anthropics/claude-code/blob/main/examples/hooks/bash_command_validator_example.py).
561
562## Hook events
563
564Each event corresponds to a point in Claude Code's lifecycle where hooks can run. The sections below are ordered to match the lifecycle: from session setup through the agentic loop to session end. Each section describes when the event fires, what matchers it supports, the JSON input it receives, and how to control behavior through output.
565
566### SessionStart
567
568Runs when Claude Code starts a new session or resumes an existing session. Useful for loading development context like existing issues or recent changes to your codebase, or setting up environment variables. For static context that does not require a script, use [CLAUDE.md](/en/memory) instead.
569
570SessionStart runs on every session, so keep these hooks fast.
571
572The matcher value corresponds to how the session was initiated:
573
574| Matcher | When it fires |
575| :-------- | :------------------------------------- |
576| `startup` | New session |
577| `resume` | `--resume`, `--continue`, or `/resume` |
578| `clear` | `/clear` |
579| `compact` | Auto or manual compaction |
580
581#### SessionStart input
582
583In addition to the [common input fields](#common-input-fields), SessionStart hooks receive `source`, `model`, and optionally `agent_type`. The `source` field indicates how the session started: `"startup"` for new sessions, `"resume"` for resumed sessions, `"clear"` after `/clear`, or `"compact"` after compaction. The `model` field contains the model identifier. If you start Claude Code with `claude --agent <name>`, an `agent_type` field contains the agent name.
536 584
537```json theme={null}585```json theme={null}
538{586{
540 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",588 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
541 "cwd": "/Users/...",589 "cwd": "/Users/...",
542 "permission_mode": "default",590 "permission_mode": "default",
543 "hook_event_name": "PreToolUse",591 "hook_event_name": "SessionStart",
544 "tool_name": "Write",592 "source": "startup",
545 "tool_input": {593 "model": "claude-sonnet-4-6"
546 "file_path": "/path/to/file.txt",
547 "content": "file content"
548 },
549 "tool_use_id": "toolu_01ABC123..."
550}594}
551```595```
552 596
553| Field | Type | Description |597#### SessionStart decision control
554| :---------- | :----- | :--------------------------------- |598
555| `file_path` | string | Absolute path to the file to write |599Any text your hook script prints to stdout is added as context for Claude. In addition to the [JSON output fields](#json-output) available to all hooks, you can return these event-specific fields:
556| `content` | string | Content to write to the file |
557 600
558#### Edit tool601| Field | Description |
602| :------------------ | :------------------------------------------------------------------------ |
603| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |
604
605```json theme={null}
606{
607 "hookSpecificOutput": {
608 "hookEventName": "SessionStart",
609 "additionalContext": "My additional context here"
610 }
611}
612```
613
614#### Persist environment variables
615
616SessionStart 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.
617
618To set individual environment variables, write `export` statements to `CLAUDE_ENV_FILE`. Use append (`>>`) to preserve variables set by other hooks:
619
620```bash theme={null}
621#!/bin/bash
622
623if [ -n "$CLAUDE_ENV_FILE" ]; then
624 echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
625 echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
626 echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
627fi
628
629exit 0
630```
631
632To capture all environment changes from setup commands, compare the exported variables before and after:
633
634```bash theme={null}
635#!/bin/bash
636
637ENV_BEFORE=$(export -p | sort)
638
639# Run your setup commands that modify the environment
640source ~/.nvm/nvm.sh
641nvm use 20
642
643if [ -n "$CLAUDE_ENV_FILE" ]; then
644 ENV_AFTER=$(export -p | sort)
645 comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
646fi
647
648exit 0
649```
650
651Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.
652
653<Note>
654 `CLAUDE_ENV_FILE` is available for SessionStart hooks. Other hook types do not have access to this variable.
655</Note>
656
657### UserPromptSubmit
658
659Runs when the user submits a prompt, before Claude processes it. This allows you
660to add additional context based on the prompt/conversation, validate prompts, or
661block certain types of prompts.
662
663#### UserPromptSubmit input
664
665In addition to the [common input fields](#common-input-fields), UserPromptSubmit hooks receive the `prompt` field containing the text the user submitted.
559 666
560```json theme={null}667```json theme={null}
561{668{
563 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",670 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
564 "cwd": "/Users/...",671 "cwd": "/Users/...",
565 "permission_mode": "default",672 "permission_mode": "default",
566 "hook_event_name": "PreToolUse",673 "hook_event_name": "UserPromptSubmit",
567 "tool_name": "Edit",674 "prompt": "Write a function to calculate the factorial of a number"
568 "tool_input": {675}
569 "file_path": "/path/to/file.txt",676```
570 "old_string": "original text",677
571 "new_string": "replacement text"678#### UserPromptSubmit decision control
679
680`UserPromptSubmit` hooks can control whether a user prompt is processed and add context. All [JSON output fields](#json-output) are available.
681
682There are two ways to add context to the conversation on exit code 0:
683
684* **Plain text stdout**: any non-JSON text written to stdout is added as context
685* **JSON with `additionalContext`**: use the JSON format below for more control. The `additionalContext` field is added as context
686
687Plain stdout is shown as hook output in the transcript. The `additionalContext` field is added more discretely.
688
689To block a prompt, return a JSON object with `decision` set to `"block"`:
690
691| Field | Description |
692| :------------------ | :----------------------------------------------------------------------------------------------------------------- |
693| `decision` | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |
694| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |
695| `additionalContext` | String added to Claude's context |
696
697```json theme={null}
698{
699 "decision": "block",
700 "reason": "Explanation for decision",
701 "hookSpecificOutput": {
702 "hookEventName": "UserPromptSubmit",
703 "additionalContext": "My additional context here"
704 }
705}
706```
707
708<Note>
709 The JSON format isn't required for simple use cases. To add context, you can print plain text to stdout with exit code 0. Use JSON when you need to
710 block prompts or want more structured control.
711</Note>
712
713### PreToolUse
714
715Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name: `Bash`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Task`, `WebFetch`, `WebSearch`, and any [MCP tool names](#match-mcp-tools).
716
717Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.
718
719#### PreToolUse input
720
721In addition to the [common input fields](#common-input-fields), PreToolUse hooks receive `tool_name`, `tool_input`, and `tool_use_id`. The `tool_input` fields depend on the tool:
722
723##### Bash
724
725Executes shell commands.
726
727| Field | Type | Example | Description |
728| :------------------ | :------ | :----------------- | :-------------------------------------------- |
729| `command` | string | `"npm test"` | The shell command to execute |
730| `description` | string | `"Run test suite"` | Optional description of what the command does |
731| `timeout` | number | `120000` | Optional timeout in milliseconds |
732| `run_in_background` | boolean | `false` | Whether to run the command in background |
733
734##### Write
735
736Creates or overwrites a file.
737
738| Field | Type | Example | Description |
739| :---------- | :----- | :-------------------- | :--------------------------------- |
740| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to write |
741| `content` | string | `"file content"` | Content to write to the file |
742
743##### Edit
744
745Replaces a string in an existing file.
746
747| Field | Type | Example | Description |
748| :------------ | :------ | :-------------------- | :--------------------------------- |
749| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to edit |
750| `old_string` | string | `"original text"` | Text to find and replace |
751| `new_string` | string | `"replacement text"` | Replacement text |
752| `replace_all` | boolean | `false` | Whether to replace all occurrences |
753
754##### Read
755
756Reads file contents.
757
758| Field | Type | Example | Description |
759| :---------- | :----- | :-------------------- | :----------------------------------------- |
760| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to read |
761| `offset` | number | `10` | Optional line number to start reading from |
762| `limit` | number | `50` | Optional number of lines to read |
763
764##### Glob
765
766Finds files matching a glob pattern.
767
768| Field | Type | Example | Description |
769| :-------- | :----- | :--------------- | :--------------------------------------------------------------------- |
770| `pattern` | string | `"**/*.ts"` | Glob pattern to match files against |
771| `path` | string | `"/path/to/dir"` | Optional directory to search in. Defaults to current working directory |
772
773##### Grep
774
775Searches file contents with regular expressions.
776
777| Field | Type | Example | Description |
778| :------------ | :------ | :--------------- | :------------------------------------------------------------------------------------ |
779| `pattern` | string | `"TODO.*fix"` | Regular expression pattern to search for |
780| `path` | string | `"/path/to/dir"` | Optional file or directory to search in |
781| `glob` | string | `"*.ts"` | Optional glob pattern to filter files |
782| `output_mode` | string | `"content"` | `"content"`, `"files_with_matches"`, or `"count"`. Defaults to `"files_with_matches"` |
783| `-i` | boolean | `true` | Case insensitive search |
784| `multiline` | boolean | `false` | Enable multiline matching |
785
786##### WebFetch
787
788Fetches and processes web content.
789
790| Field | Type | Example | Description |
791| :------- | :----- | :---------------------------- | :----------------------------------- |
792| `url` | string | `"https://example.com/api"` | URL to fetch content from |
793| `prompt` | string | `"Extract the API endpoints"` | Prompt to run on the fetched content |
794
795##### WebSearch
796
797Searches the web.
798
799| Field | Type | Example | Description |
800| :---------------- | :----- | :----------------------------- | :------------------------------------------------ |
801| `query` | string | `"react hooks best practices"` | Search query |
802| `allowed_domains` | array | `["docs.example.com"]` | Optional: only include results from these domains |
803| `blocked_domains` | array | `["spam.example.com"]` | Optional: exclude results from these domains |
804
805##### Task
806
807Spawns a [subagent](/en/sub-agents).
808
809| Field | Type | Example | Description |
810| :-------------- | :----- | :------------------------- | :------------------------------------------- |
811| `prompt` | string | `"Find all API endpoints"` | The task for the agent to perform |
812| `description` | string | `"Find API endpoints"` | Short description of the task |
813| `subagent_type` | string | `"Explore"` | Type of specialized agent to use |
814| `model` | string | `"sonnet"` | Optional model alias to override the default |
815
816#### PreToolUse decision control
817
818`PreToolUse` hooks can control whether a tool call proceeds. Unlike other hooks that use a top-level `decision` field, PreToolUse returns its decision inside a `hookSpecificOutput` object. This gives it richer control: three outcomes (allow, deny, or ask) plus the ability to modify tool input before execution.
819
820| Field | Description |
821| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
822| `permissionDecision` | `"allow"` bypasses the permission system, `"deny"` prevents the tool call, `"ask"` prompts the user to confirm |
823| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude |
824| `updatedInput` | Modifies the tool's input parameters before execution. Combine with `"allow"` to auto-approve, or `"ask"` to show the modified input to the user |
825| `additionalContext` | String added to Claude's context before the tool executes |
826
827```json theme={null}
828{
829 "hookSpecificOutput": {
830 "hookEventName": "PreToolUse",
831 "permissionDecision": "allow",
832 "permissionDecisionReason": "My reason here",
833 "updatedInput": {
834 "field_to_modify": "new value"
572 },835 },
573 "tool_use_id": "toolu_01ABC123..."836 "additionalContext": "Current environment: production. Proceed with caution."
837 }
574}838}
575```839```
576 840
577| Field | Type | Description |841<Note>
578| :------------ | :------ | :-------------------------------------------------- |842 PreToolUse previously used top-level `decision` and `reason` fields, but these are deprecated for this event. Use `hookSpecificOutput.permissionDecision` and `hookSpecificOutput.permissionDecisionReason` instead. The deprecated values `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively. Other events like PostToolUse and Stop continue to use top-level `decision` and `reason` as their current format.
579| `file_path` | string | Absolute path to the file to edit |843</Note>
580| `old_string` | string | Text to find and replace |844
581| `new_string` | string | Replacement text |845### PermissionRequest
582| `replace_all` | boolean | Whether to replace all occurrences (default: false) |846
847Runs when the user is shown a permission dialog.
848Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.
849
850Matches on tool name, same values as PreToolUse.
583 851
584#### Read tool852#### PermissionRequest input
853
854PermissionRequest 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. The difference is when the hook fires: PermissionRequest hooks run when a permission dialog is about to be shown to the user, while PreToolUse hooks run before tool execution regardless of permission status.
585 855
586```json theme={null}856```json theme={null}
587{857{
589 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",859 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
590 "cwd": "/Users/...",860 "cwd": "/Users/...",
591 "permission_mode": "default",861 "permission_mode": "default",
592 "hook_event_name": "PreToolUse",862 "hook_event_name": "PermissionRequest",
593 "tool_name": "Read",863 "tool_name": "Bash",
594 "tool_input": {864 "tool_input": {
595 "file_path": "/path/to/file.txt"865 "command": "rm -rf node_modules",
866 "description": "Remove node_modules directory"
596 },867 },
597 "tool_use_id": "toolu_01ABC123..."868 "permission_suggestions": [
869 { "type": "toolAlwaysAllow", "tool": "Bash" }
870 ]
598}871}
599```872```
600 873
601| Field | Type | Description |874#### PermissionRequest decision control
602| :---------- | :----- | :----------------------------------------- |875
603| `file_path` | string | Absolute path to the file to read |876`PermissionRequest` hooks can allow or deny permission requests. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return a `decision` object with these event-specific fields:
604| `offset` | number | Optional line number to start reading from |877
605| `limit` | number | Optional number of lines to read |878| Field | Description |
879| :------------------- | :------------------------------------------------------------------------------------------------------------- |
880| `behavior` | `"allow"` grants the permission, `"deny"` denies it |
881| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution |
882| `updatedPermissions` | For `"allow"` only: applies permission rule updates, equivalent to the user selecting an "always allow" option |
883| `message` | For `"deny"` only: tells Claude why the permission was denied |
884| `interrupt` | For `"deny"` only: if `true`, stops Claude |
885
886```json theme={null}
887{
888 "hookSpecificOutput": {
889 "hookEventName": "PermissionRequest",
890 "decision": {
891 "behavior": "allow",
892 "updatedInput": {
893 "command": "npm run lint"
894 }
895 }
896 }
897}
898```
899
900### PostToolUse
901
902Runs immediately after a tool completes successfully.
903
904Matches on tool name, same values as PreToolUse.
606 905
607### PostToolUse Input906#### PostToolUse input
608 907
609The exact schema for `tool_input` and `tool_response` depends on the tool.908`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.
610 909
611```json theme={null}910```json theme={null}
612{911{
628}927}
629```928```
630 929
631### Notification Input930#### PostToolUse decision control
931
932`PostToolUse` hooks can provide feedback to Claude after tool execution. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:
933
934| Field | Description |
935| :--------------------- | :----------------------------------------------------------------------------------------- |
936| `decision` | `"block"` prompts Claude with the `reason`. Omit to allow the action to proceed |
937| `reason` | Explanation shown to Claude when `decision` is `"block"` |
938| `additionalContext` | Additional context for Claude to consider |
939| `updatedMCPToolOutput` | For [MCP tools](#match-mcp-tools) only: replaces the tool's output with the provided value |
940
941```json theme={null}
942{
943 "decision": "block",
944 "reason": "Explanation for decision",
945 "hookSpecificOutput": {
946 "hookEventName": "PostToolUse",
947 "additionalContext": "Additional information for Claude"
948 }
949}
950```
951
952### PostToolUseFailure
953
954Runs when a tool execution fails. This event fires for tool calls that throw errors or return failure results. Use this to log failures, send alerts, or provide corrective feedback to Claude.
955
956Matches on tool name, same values as PreToolUse.
957
958#### PostToolUseFailure input
959
960PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as PostToolUse, along with error information as top-level fields:
961
962```json theme={null}
963{
964 "session_id": "abc123",
965 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
966 "cwd": "/Users/...",
967 "permission_mode": "default",
968 "hook_event_name": "PostToolUseFailure",
969 "tool_name": "Bash",
970 "tool_input": {
971 "command": "npm test",
972 "description": "Run test suite"
973 },
974 "tool_use_id": "toolu_01ABC123...",
975 "error": "Command exited with non-zero status code 1",
976 "is_interrupt": false
977}
978```
979
980| Field | Description |
981| :------------- | :------------------------------------------------------------------------------ |
982| `error` | String describing what went wrong |
983| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |
984
985#### PostToolUseFailure decision control
986
987`PostToolUseFailure` hooks can provide context to Claude after a tool failure. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:
988
989| Field | Description |
990| :------------------ | :------------------------------------------------------------ |
991| `additionalContext` | Additional context for Claude to consider alongside the error |
992
993```json theme={null}
994{
995 "hookSpecificOutput": {
996 "hookEventName": "PostToolUseFailure",
997 "additionalContext": "Additional information about the failure for Claude"
998 }
999}
1000```
1001
1002### Notification
1003
1004Runs when Claude Code sends notifications. Matches on notification type: `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`. Omit the matcher to run hooks for all notification types.
1005
1006Use separate matchers to run different handlers depending on the notification type. This configuration triggers a permission-specific alert script when Claude needs permission approval and a different notification when Claude has been idle:
1007
1008```json theme={null}
1009{
1010 "hooks": {
1011 "Notification": [
1012 {
1013 "matcher": "permission_prompt",
1014 "hooks": [
1015 {
1016 "type": "command",
1017 "command": "/path/to/permission-alert.sh"
1018 }
1019 ]
1020 },
1021 {
1022 "matcher": "idle_prompt",
1023 "hooks": [
1024 {
1025 "type": "command",
1026 "command": "/path/to/idle-notification.sh"
1027 }
1028 ]
1029 }
1030 ]
1031 }
1032}
1033```
1034
1035#### Notification input
1036
1037In addition to the [common input fields](#common-input-fields), Notification hooks receive `message` with the notification text, an optional `title`, and `notification_type` indicating which type fired.
1038
1039```json theme={null}
1040{
1041 "session_id": "abc123",
1042 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1043 "cwd": "/Users/...",
1044 "permission_mode": "default",
1045 "hook_event_name": "Notification",
1046 "message": "Claude needs your permission to use Bash",
1047 "title": "Permission needed",
1048 "notification_type": "permission_prompt"
1049}
1050```
1051
1052Notification hooks cannot block or modify notifications. In addition to the [JSON output fields](#json-output) available to all hooks, you can return `additionalContext` to add context to the conversation:
1053
1054| Field | Description |
1055| :------------------ | :------------------------------- |
1056| `additionalContext` | String added to Claude's context |
1057
1058### SubagentStart
1059
1060Runs when a Claude Code subagent is spawned via the Task tool. Supports matchers to filter by agent type name (built-in agents like `Bash`, `Explore`, `Plan`, or custom agent names from `.claude/agents/`).
1061
1062#### SubagentStart input
1063
1064In addition to the [common input fields](#common-input-fields), SubagentStart hooks receive `agent_id` with the unique identifier for the subagent and `agent_type` with the agent name (built-in agents like `"Bash"`, `"Explore"`, `"Plan"`, or custom agent names).
1065
1066```json theme={null}
1067{
1068 "session_id": "abc123",
1069 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1070 "cwd": "/Users/...",
1071 "permission_mode": "default",
1072 "hook_event_name": "SubagentStart",
1073 "agent_id": "agent-abc123",
1074 "agent_type": "Explore"
1075}
1076```
1077
1078SubagentStart hooks cannot block subagent creation, but they can inject context into the subagent. In addition to the [JSON output fields](#json-output) available to all hooks, you can return:
1079
1080| Field | Description |
1081| :------------------ | :------------------------------------- |
1082| `additionalContext` | String added to the subagent's context |
1083
1084```json theme={null}
1085{
1086 "hookSpecificOutput": {
1087 "hookEventName": "SubagentStart",
1088 "additionalContext": "Follow security guidelines for this task"
1089 }
1090}
1091```
1092
1093### SubagentStop
1094
1095Runs when a Claude Code subagent has finished responding. Matches on agent type, same values as SubagentStart.
1096
1097#### SubagentStop input
1098
1099In addition to the [common input fields](#common-input-fields), SubagentStop hooks receive `stop_hook_active`, `agent_id`, `agent_type`, `agent_transcript_path`, and `last_assistant_message`. The `agent_type` field is the value used for matcher filtering. The `transcript_path` is the main session's transcript, while `agent_transcript_path` is the subagent's own transcript stored in a nested `subagents/` folder. The `last_assistant_message` field contains the text content of the subagent's final response, so hooks can access it without parsing the transcript file.
1100
1101```json theme={null}
1102{
1103 "session_id": "abc123",
1104 "transcript_path": "~/.claude/projects/.../abc123.jsonl",
1105 "cwd": "/Users/...",
1106 "permission_mode": "default",
1107 "hook_event_name": "SubagentStop",
1108 "stop_hook_active": false,
1109 "agent_id": "def456",
1110 "agent_type": "Explore",
1111 "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
1112 "last_assistant_message": "Analysis complete. Found 3 potential issues..."
1113}
1114```
1115
1116SubagentStop hooks use the same decision control format as [Stop hooks](#stop-decision-control).
1117
1118### Stop
1119
1120Runs when the main Claude Code agent has finished responding. Does not run if
1121the stoppage occurred due to a user interrupt.
1122
1123#### Stop input
1124
1125In addition to the [common input fields](#common-input-fields), Stop hooks receive `stop_hook_active` and `last_assistant_message`. The `stop_hook_active` field is `true` when Claude Code is already continuing as a result of a stop hook. Check this value or process the transcript to prevent Claude Code from running indefinitely. The `last_assistant_message` field contains the text content of Claude's final response, so hooks can access it without parsing the transcript file.
1126
1127```json theme={null}
1128{
1129 "session_id": "abc123",
1130 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1131 "cwd": "/Users/...",
1132 "permission_mode": "default",
1133 "hook_event_name": "Stop",
1134 "stop_hook_active": true,
1135 "last_assistant_message": "I've completed the refactoring. Here's a summary..."
1136}
1137```
1138
1139#### Stop decision control
1140
1141`Stop` and `SubagentStop` hooks can control whether Claude continues. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:
1142
1143| Field | Description |
1144| :--------- | :------------------------------------------------------------------------- |
1145| `decision` | `"block"` prevents Claude from stopping. Omit to allow Claude to stop |
1146| `reason` | Required when `decision` is `"block"`. Tells Claude why it should continue |
1147
1148```json theme={null}
1149{
1150 "decision": "block",
1151 "reason": "Must be provided when Claude is blocked from stopping"
1152}
1153```
1154
1155### TeammateIdle
1156
1157Runs when an [agent team](/en/agent-teams) teammate is about to go idle after finishing its turn. Use this to enforce quality gates before a teammate stops working, such as requiring passing lint checks or verifying that output files exist.
1158
1159When a `TeammateIdle` hook exits with code 2, the teammate receives the stderr message as feedback and continues working instead of going idle. TeammateIdle hooks do not support matchers and fire on every occurrence.
1160
1161#### TeammateIdle input
1162
1163In addition to the [common input fields](#common-input-fields), TeammateIdle hooks receive `teammate_name` and `team_name`.
1164
1165```json theme={null}
1166{
1167 "session_id": "abc123",
1168 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1169 "cwd": "/Users/...",
1170 "permission_mode": "default",
1171 "hook_event_name": "TeammateIdle",
1172 "teammate_name": "researcher",
1173 "team_name": "my-project"
1174}
1175```
1176
1177| Field | Description |
1178| :-------------- | :-------------------------------------------- |
1179| `teammate_name` | Name of the teammate that is about to go idle |
1180| `team_name` | Name of the team |
1181
1182#### TeammateIdle decision control
1183
1184TeammateIdle hooks use exit codes only, not JSON decision control. This example checks that a build artifact exists before allowing a teammate to go idle:
1185
1186```bash theme={null}
1187#!/bin/bash
1188
1189if [ ! -f "./dist/output.js" ]; then
1190 echo "Build artifact missing. Run the build before stopping." >&2
1191 exit 2
1192fi
1193
1194exit 0
1195```
1196
1197### TaskCompleted
1198
1199Runs when a task is being marked as completed. This fires in two situations: when any agent explicitly marks a task as completed through the TaskUpdate tool, or when an [agent team](/en/agent-teams) teammate finishes its turn with in-progress tasks. Use this to enforce completion criteria like passing tests or lint checks before a task can close.
1200
1201When a `TaskCompleted` hook exits with code 2, the task is not marked as completed and the stderr message is fed back to the model as feedback. TaskCompleted hooks do not support matchers and fire on every occurrence.
1202
1203#### TaskCompleted input
1204
1205In addition to the [common input fields](#common-input-fields), TaskCompleted hooks receive `task_id`, `task_subject`, and optionally `task_description`, `teammate_name`, and `team_name`.
1206
1207```json theme={null}
1208{
1209 "session_id": "abc123",
1210 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1211 "cwd": "/Users/...",
1212 "permission_mode": "default",
1213 "hook_event_name": "TaskCompleted",
1214 "task_id": "task-001",
1215 "task_subject": "Implement user authentication",
1216 "task_description": "Add login and signup endpoints",
1217 "teammate_name": "implementer",
1218 "team_name": "my-project"
1219}
1220```
1221
1222| Field | Description |
1223| :----------------- | :------------------------------------------------------ |
1224| `task_id` | Identifier of the task being completed |
1225| `task_subject` | Title of the task |
1226| `task_description` | Detailed description of the task. May be absent |
1227| `teammate_name` | Name of the teammate completing the task. May be absent |
1228| `team_name` | Name of the team. May be absent |
1229
1230#### TaskCompleted decision control
1231
1232TaskCompleted hooks use exit codes only, not JSON decision control. This example runs tests and blocks task completion if they fail:
1233
1234```bash theme={null}
1235#!/bin/bash
1236INPUT=$(cat)
1237TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
1238
1239# Run the test suite
1240if ! npm test 2>&1; then
1241 echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
1242 exit 2
1243fi
1244
1245exit 0
1246```
1247
1248### ConfigChange
1249
1250Runs when a configuration file changes during a session. Use this to audit settings changes, enforce security policies, or block unauthorized modifications to configuration files.
1251
1252ConfigChange hooks fire for changes to settings files, managed policy settings, and skill files. The `source` field in the input tells you which type of configuration changed, and the optional `file_path` field provides the path to the changed file.
1253
1254The matcher filters on the configuration source:
1255
1256| Matcher | When it fires |
1257| :----------------- | :---------------------------------------- |
1258| `user_settings` | `~/.claude/settings.json` changes |
1259| `project_settings` | `.claude/settings.json` changes |
1260| `local_settings` | `.claude/settings.local.json` changes |
1261| `policy_settings` | Managed policy settings change |
1262| `skills` | A skill file in `.claude/skills/` changes |
1263
1264This example logs all configuration changes for security auditing:
632 1265
633```json theme={null}1266```json theme={null}
634{1267{
635 "session_id": "abc123",1268 "hooks": {
636 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1269 "ConfigChange": [
637 "cwd": "/Users/...",1270 {
638 "permission_mode": "default",1271 "hooks": [
639 "hook_event_name": "Notification",1272 {
640 "message": "Claude needs your permission to use Bash",1273 "type": "command",
641 "notification_type": "permission_prompt"1274 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit-config-change.sh"
1275 }
1276 ]
1277 }
1278 ]
1279 }
642}1280}
643```1281```
644 1282
645### UserPromptSubmit Input1283#### ConfigChange input
1284
1285In addition to the [common input fields](#common-input-fields), ConfigChange hooks receive `source` and optionally `file_path`. The `source` field indicates which configuration type changed, and `file_path` provides the path to the specific file that was modified.
646 1286
647```json theme={null}1287```json theme={null}
648{1288{
650 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1290 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
651 "cwd": "/Users/...",1291 "cwd": "/Users/...",
652 "permission_mode": "default",1292 "permission_mode": "default",
653 "hook_event_name": "UserPromptSubmit",1293 "hook_event_name": "ConfigChange",
654 "prompt": "Write a function to calculate the factorial of a number"1294 "source": "project_settings",
1295 "file_path": "/Users/.../my-project/.claude/settings.json"
655}1296}
656```1297```
657 1298
658### Stop and SubagentStop Input1299#### ConfigChange decision control
659 1300
660`stop_hook_active` is true when Claude Code is already continuing as a result of1301ConfigChange hooks can block configuration changes from taking effect. Use exit code 2 or a JSON `decision` to prevent the change. When blocked, the new settings are not applied to the running session.
661a stop hook. Check this value or process the transcript to prevent Claude Code1302
662from running indefinitely.1303| Field | Description |
1304| :--------- | :--------------------------------------------------------------------------------------- |
1305| `decision` | `"block"` prevents the configuration change from being applied. Omit to allow the change |
1306| `reason` | Explanation shown to the user when `decision` is `"block"` |
663 1307
664```json theme={null}1308```json theme={null}
665{1309{
666 "session_id": "abc123",1310 "decision": "block",
667 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1311 "reason": "Configuration changes to project settings require admin approval"
668 "permission_mode": "default",
669 "hook_event_name": "Stop",
670 "stop_hook_active": true
671}1312}
672```1313```
673 1314
674### PreCompact Input1315`policy_settings` changes cannot be blocked. Hooks still fire for `policy_settings` sources, so you can use them for audit logging, but any blocking decision is ignored. This ensures enterprise-managed settings always take effect.
675 1316
676For `manual`, `custom_instructions` comes from what the user passes into1317### WorktreeCreate
677`/compact`. For `auto`, `custom_instructions` is empty.
678 1318
679```json theme={null}1319When you run `claude --worktree` or a [subagent uses `isolation: "worktree"`](/en/sub-agents#choose-the-subagent-scope), Claude Code creates an isolated working copy using `git worktree`. If you configure a WorktreeCreate hook, it replaces the default git behavior, letting you use a different version control system like SVN, Perforce, or Mercurial.
680{
681 "session_id": "abc123",
682 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
683 "permission_mode": "default",
684 "hook_event_name": "PreCompact",
685 "trigger": "manual",
686 "custom_instructions": ""
687}
688```
689 1320
690### SessionStart Input1321The hook must print the absolute path to the created worktree directory on stdout. Claude Code uses this path as the working directory for the isolated session.
1322
1323This example creates an SVN working copy and prints the path for Claude Code to use. Replace the repository URL with your own:
691 1324
692```json theme={null}1325```json theme={null}
693{1326{
694 "session_id": "abc123",1327 "hooks": {
695 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1328 "WorktreeCreate": [
696 "permission_mode": "default",1329 {
697 "hook_event_name": "SessionStart",1330 "hooks": [
698 "source": "startup"1331 {
1332 "type": "command",
1333 "command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
1334 }
1335 ]
1336 }
1337 ]
1338 }
699}1339}
700```1340```
701 1341
702### SessionEnd Input1342The hook reads the worktree `name` from the JSON input on stdin, checks out a fresh copy into a new directory, and prints the directory path. The `echo` on the last line is what Claude Code reads as the worktree path. Redirect any other output to stderr so it doesn't interfere with the path.
1343
1344#### WorktreeCreate input
1345
1346In addition to the [common input fields](#common-input-fields), WorktreeCreate hooks receive the `name` field. This is a slug identifier for the new worktree, either specified by the user or auto-generated (for example, `bold-oak-a3f2`).
703 1347
704```json theme={null}1348```json theme={null}
705{1349{
706 "session_id": "abc123",1350 "session_id": "abc123",
707 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1351 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
708 "cwd": "/Users/...",1352 "cwd": "/Users/...",
709 "permission_mode": "default",1353 "hook_event_name": "WorktreeCreate",
710 "hook_event_name": "SessionEnd",1354 "name": "feature-auth"
711 "reason": "exit"
712}1355}
713```1356```
714 1357
715## Hook Output1358#### WorktreeCreate output
716
717There are two mutually exclusive ways for hooks to return output back to Claude Code. The output
718communicates whether to block and any feedback that should be shown to Claude
719and the user.
720
721### Simple: Exit Code
722
723Hooks communicate status through exit codes, stdout, and stderr:
724
725* **Exit code 0**: Success. `stdout` is shown to the user in verbose mode
726 (ctrl+o), except for `UserPromptSubmit` and `SessionStart`, where stdout is
727 added to the context. JSON output in `stdout` is parsed for structured control
728 (see [Advanced: JSON Output](#advanced-json-output)).
729* **Exit code 2**: Blocking error. Only `stderr` is used as the error message
730 and fed back to Claude. The format is `[command]: {stderr}`. JSON in `stdout`
731 is **not** processed for exit code 2. See per-hook-event behavior below.
732* **Other exit codes**: Non-blocking error. `stderr` is shown to the user in verbose mode (ctrl+o) with
733 format `Failed with non-blocking status code: {stderr}`. If `stderr` is empty,
734 it shows `No stderr output`. Execution continues.
735
736<Warning>
737 Reminder: Claude Code does not see stdout if the exit code is 0, except for
738 the `UserPromptSubmit` hook where stdout is injected as context.
739</Warning>
740
741#### Exit Code 2 Behavior
742
743| Hook Event | Behavior |
744| ------------------- | ------------------------------------------------------------------ |
745| `PreToolUse` | Blocks the tool call, shows stderr to Claude |
746| `PermissionRequest` | Denies the permission, shows stderr to Claude |
747| `PostToolUse` | Shows stderr to Claude (tool already ran) |
748| `Notification` | N/A, shows stderr to user only |
749| `UserPromptSubmit` | Blocks prompt processing, erases prompt, shows stderr to user only |
750| `Stop` | Blocks stoppage, shows stderr to Claude |
751| `SubagentStop` | Blocks stoppage, shows stderr to Claude subagent |
752| `PreCompact` | N/A, shows stderr to user only |
753| `SessionStart` | N/A, shows stderr to user only |
754| `SessionEnd` | N/A, shows stderr to user only |
755 1359
756### Advanced: JSON Output1360The hook must print the absolute path to the created worktree directory on stdout. If the hook fails or produces no output, worktree creation fails with an error.
757 1361
758Hooks can return structured JSON in `stdout` for more sophisticated control.1362WorktreeCreate hooks do not use the standard allow/block decision model. Instead, the hook's success or failure determines the outcome. Only `type: "command"` hooks are supported.
759 1363
760<Warning>1364### WorktreeRemove
761 JSON output is only processed when the hook exits with code 0. If your hook
762 exits with code 2 (blocking error), `stderr` text is used directly—any JSON in `stdout`
763 is ignored. For other non-zero exit codes, only `stderr` is shown to the user in verbose mode (ctrl+o).
764</Warning>
765 1365
766#### Common JSON Fields1366The cleanup counterpart to [WorktreeCreate](#worktreecreate). This hook fires when a worktree is being removed, either when you exit a `--worktree` session and choose to remove it, or when a subagent with `isolation: "worktree"` finishes. For git-based worktrees, Claude handles cleanup automatically with `git worktree remove`. If you configured a WorktreeCreate hook for a non-git version control system, pair it with a WorktreeRemove hook to handle cleanup. Without one, the worktree directory is left on disk.
767 1367
768All hook types can include these optional fields:1368Claude Code passes the path that WorktreeCreate printed on stdout as `worktree_path` in the hook input. This example reads that path and removes the directory:
769 1369
770```json theme={null}1370```json theme={null}
771{1371{
772 "continue": true, // Whether Claude should continue after hook execution (default: true)1372 "hooks": {
773 "stopReason": "string", // Message shown when continue is false1373 "WorktreeRemove": [
774 1374 {
775 "suppressOutput": true, // Hide stdout from transcript mode (default: false)1375 "hooks": [
776 "systemMessage": "string" // Optional warning message shown to the user1376 {
1377 "type": "command",
1378 "command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
1379 }
1380 ]
1381 }
1382 ]
1383 }
777}1384}
778```1385```
779 1386
780If `continue` is false, Claude stops processing after the hooks run.1387#### WorktreeRemove input
781 1388
782* For `PreToolUse`, this is different from `"permissionDecision": "deny"`, which1389In addition to the [common input fields](#common-input-fields), WorktreeRemove hooks receive the `worktree_path` field, which is the absolute path to the worktree being removed.
783 only blocks a specific tool call and provides automatic feedback to Claude.
784* For `PostToolUse`, this is different from `"decision": "block"`, which
785 provides automated feedback to Claude.
786* For `UserPromptSubmit`, this prevents the prompt from being processed.
787* For `Stop` and `SubagentStop`, this takes precedence over any
788 `"decision": "block"` output.
789* In all cases, `"continue" = false` takes precedence over any
790 `"decision": "block"` output.
791 1390
792`stopReason` accompanies `continue` with a reason shown to the user, not shown1391```json theme={null}
793to Claude.1392{
1393 "session_id": "abc123",
1394 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1395 "cwd": "/Users/...",
1396 "hook_event_name": "WorktreeRemove",
1397 "worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
1398}
1399```
794 1400
795#### `PreToolUse` Decision Control1401WorktreeRemove hooks have no decision control. They cannot block worktree removal but can perform cleanup tasks like removing version control state or archiving changes. Hook failures are logged in debug mode only. Only `type: "command"` hooks are supported.
796 1402
797`PreToolUse` hooks can control whether a tool call proceeds.1403### PreCompact
798 1404
799* `"allow"` bypasses the permission system. `permissionDecisionReason` is shown1405Runs before Claude Code is about to run a compact operation.
800 to the user but not to Claude.
801* `"deny"` prevents the tool call from executing. `permissionDecisionReason` is
802 shown to Claude.
803* `"ask"` asks the user to confirm the tool call in the UI.
804 `permissionDecisionReason` is shown to the user but not to Claude.
805 1406
806Additionally, hooks can modify tool inputs before execution using `updatedInput`:1407The matcher value indicates whether compaction was triggered manually or automatically:
807 1408
808* `updatedInput` modifies the tool's input parameters before the tool executes1409| Matcher | When it fires |
809* Combine with `"permissionDecision": "allow"` to modify the input and auto-approve the tool call1410| :------- | :------------------------------------------- |
810* Combine with `"permissionDecision": "ask"` to modify the input and show it to the user for confirmation1411| `manual` | `/compact` |
1412| `auto` | Auto-compact when the context window is full |
811 1413
812Hooks can also provide context to Claude using `additionalContext`:1414#### PreCompact input
813 1415
814* `"hookSpecificOutput.additionalContext"` adds a string to Claude's context before the tool executes.1416In addition to the [common input fields](#common-input-fields), PreCompact hooks receive `trigger` and `custom_instructions`. For `manual`, `custom_instructions` contains what the user passes into `/compact`. For `auto`, `custom_instructions` is empty.
815 1417
816```json theme={null}1418```json theme={null}
817{1419{
818 "hookSpecificOutput": {1420 "session_id": "abc123",
819 "hookEventName": "PreToolUse",1421 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
820 "permissionDecision": "allow",1422 "cwd": "/Users/...",
821 "permissionDecisionReason": "My reason here",1423 "permission_mode": "default",
822 "updatedInput": {1424 "hook_event_name": "PreCompact",
823 "field_to_modify": "new value"1425 "trigger": "manual",
824 },1426 "custom_instructions": ""
825 "additionalContext": "Current environment: production. Proceed with caution."
826 }
827}1427}
828```1428```
829 1429
830<Note>1430### SessionEnd
831 The `decision` and `reason` fields are deprecated for PreToolUse hooks.1431
832 Use `hookSpecificOutput.permissionDecision` and1432Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
833 `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields1433statistics, or saving session state. Supports matchers to filter by exit reason.
834 `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.1434
835</Note>1435The `reason` field in the hook input indicates why the session ended:
836 1436
837#### `PermissionRequest` Decision Control1437| Reason | Description |
1438| :---------------------------- | :----------------------------------------- |
1439| `clear` | Session cleared with `/clear` command |
1440| `logout` | User logged out |
1441| `prompt_input_exit` | User exited while prompt input was visible |
1442| `bypass_permissions_disabled` | Bypass permissions mode was disabled |
1443| `other` | Other exit reasons |
838 1444
839`PermissionRequest` hooks can allow or deny permission requests shown to the user.1445#### SessionEnd input
840 1446
841* For `"behavior": "allow"` you can also optionally pass in an `"updatedInput"` that modifies the tool's input parameters before the tool executes.1447In addition to the [common input fields](#common-input-fields), SessionEnd hooks receive a `reason` field indicating why the session ended. See the [reason table](#sessionend) above for all values.
842* For `"behavior": "deny"` you can also optionally pass in a `"message"` string that tells the model why the permission was denied, and a boolean `"interrupt"` which will stop Claude.
843 1448
844```json theme={null}1449```json theme={null}
845{1450{
846 "hookSpecificOutput": {1451 "session_id": "abc123",
847 "hookEventName": "PermissionRequest",1452 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
848 "decision": {1453 "cwd": "/Users/...",
849 "behavior": "allow",1454 "permission_mode": "default",
850 "updatedInput": {1455 "hook_event_name": "SessionEnd",
851 "command": "npm run lint"1456 "reason": "other"
852 }
853 }
854 }
855}1457}
856```1458```
857 1459
858#### `PostToolUse` Decision Control1460SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.
859 1461
860`PostToolUse` hooks can provide feedback to Claude after tool execution.1462## Prompt-based hooks
861 1463
862* `"block"` automatically prompts Claude with `reason`.1464In addition to Bash command hooks (`type: "command"`), Claude Code supports prompt-based hooks (`type: "prompt"`) that use an LLM to evaluate whether to allow or block an action, and agent hooks (`type: "agent"`) that spawn an agentic verifier with tool access. Not all events support every hook type.
863* `undefined` does nothing. `reason` is ignored.
864* `"hookSpecificOutput.additionalContext"` adds context for Claude to consider.
865 1465
866```json theme={null}1466Events that support all three hook types (`command`, `prompt`, and `agent`):
867{1467
868 "decision": "block" | undefined,1468* `PermissionRequest`
869 "reason": "Explanation for decision",1469* `PostToolUse`
870 "hookSpecificOutput": {1470* `PostToolUseFailure`
871 "hookEventName": "PostToolUse",1471* `PreToolUse`
872 "additionalContext": "Additional information for Claude"1472* `Stop`
873 }1473* `SubagentStop`
874}1474* `TaskCompleted`
875```1475* `UserPromptSubmit`
876 1476
877#### `UserPromptSubmit` Decision Control1477Events that only support `type: "command"` hooks:
878 1478
879`UserPromptSubmit` hooks can control whether a user prompt is processed and add context.1479* `ConfigChange`
1480* `Notification`
1481* `PreCompact`
1482* `SessionEnd`
1483* `SessionStart`
1484* `SubagentStart`
1485* `TeammateIdle`
1486* `WorktreeCreate`
1487* `WorktreeRemove`
880 1488
881**Adding context (exit code 0):**1489### How prompt-based hooks work
882There are two ways to add context to the conversation:
883 1490
8841. **Plain text stdout** (simpler): Any non-JSON text written to stdout is added1491Instead of executing a Bash command, prompt-based hooks:
885 as context. This is the easiest way to inject information.
886 1492
8872. **JSON with `additionalContext`** (structured): Use the JSON format below for14931. Send the hook input and your prompt to a Claude model, Haiku by default
888 more control. The `additionalContext` field is added as context.14942. The LLM responds with structured JSON containing a decision
14953. Claude Code processes the decision automatically
889 1496
890Both methods work with exit code 0. Plain stdout is shown as hook output in1497### Prompt hook configuration
891the transcript; `additionalContext` is added more discretely.
892 1498
893**Blocking prompts:**1499Set `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. Claude Code sends the combined prompt and input to a fast Claude model, which returns a JSON decision.
894 1500
895* `"decision": "block"` prevents the prompt from being processed. The submitted1501This `Stop` hook asks the LLM to evaluate whether all tasks are complete before allowing Claude to finish:
896 prompt is erased from context. `"reason"` is shown to the user but not added
897 to context.
898* `"decision": undefined` (or omitted) allows the prompt to proceed normally.
899 1502
900```json theme={null}1503```json theme={null}
901{1504{
902 "decision": "block" | undefined,1505 "hooks": {
903 "reason": "Explanation for decision",1506 "Stop": [
904 "hookSpecificOutput": {1507 {
905 "hookEventName": "UserPromptSubmit",1508 "hooks": [
906 "additionalContext": "My additional context here"1509 {
1510 "type": "prompt",
1511 "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
1512 }
1513 ]
1514 }
1515 ]
907 }1516 }
908}1517}
909```1518```
910 1519
911<Note>1520| Field | Required | Description |
912 The JSON format isn't required for simple use cases. To add context, you can print plain text to stdout with exit code 0. Use JSON when you need to1521| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
913 block prompts or want more structured control.1522| `type` | yes | Must be `"prompt"` |
914</Note>1523| `prompt` | yes | The prompt text to send to the LLM. Use `$ARGUMENTS` as a placeholder for the hook input JSON. If `$ARGUMENTS` is not present, input JSON is appended to the prompt |
1524| `model` | no | Model to use for evaluation. Defaults to a fast model |
1525| `timeout` | no | Timeout in seconds. Default: 30 |
915 1526
916#### `Stop`/`SubagentStop` Decision Control1527### Response schema
917
918`Stop` and `SubagentStop` hooks can control whether Claude must continue.
919 1528
920* `"block"` prevents Claude from stopping. You must populate `reason` for Claude1529The LLM must respond with JSON containing:
921 to know how to proceed.
922* `undefined` allows Claude to stop. `reason` is ignored.
923 1530
924```json theme={null}1531```json theme={null}
925{1532{
926 "decision": "block" | undefined,1533 "ok": true | false,
927 "reason": "Must be provided when Claude is blocked from stopping"1534 "reason": "Explanation for the decision"
928}1535}
929```1536```
930 1537
931#### `SessionStart` Decision Control1538| Field | Description |
1539| :------- | :--------------------------------------------------------- |
1540| `ok` | `true` allows the action, `false` prevents it |
1541| `reason` | Required when `ok` is `false`. Explanation shown to Claude |
932 1542
933`SessionStart` hooks allow you to load in context at the start of a session.1543### Example: Multi-criteria Stop hook
934 1544
935* `"hookSpecificOutput.additionalContext"` adds the string to the context.1545This `Stop` hook uses a detailed prompt to check three conditions before allowing Claude to stop. If `"ok"` is `false`, Claude continues working with the provided reason as its next instruction. `SubagentStop` hooks use the same format to evaluate whether a [subagent](/en/sub-agents) should stop:
936* Multiple hooks' `additionalContext` values are concatenated.
937 1546
938```json theme={null}1547```json theme={null}
939{1548{
940 "hookSpecificOutput": {1549 "hooks": {
941 "hookEventName": "SessionStart",1550 "Stop": [
942 "additionalContext": "My additional context here"1551 {
1552 "hooks": [
1553 {
1554 "type": "prompt",
1555 "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
1556 "timeout": 30
1557 }
1558 ]
1559 }
1560 ]
943 }1561 }
944}1562}
945```1563```
946 1564
947#### `SessionEnd` Decision Control1565## Agent-based hooks
948
949`SessionEnd` hooks run when a session ends. They cannot block session termination
950but can perform cleanup tasks.
951
952#### Exit Code Example: Bash Command Validation
953
954```python theme={null}
955#!/usr/bin/env python3
956import json
957import re
958import sys
959
960# Define validation rules as a list of (regex pattern, message) tuples
961VALIDATION_RULES = [
962 (
963 r"\bgrep\b(?!.*\|)",
964 "Use 'rg' (ripgrep) instead of 'grep' for better performance and features",
965 ),
966 (
967 r"\bfind\s+\S+\s+-name\b",
968 "Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",
969 ),
970]
971
972
973def validate_command(command: str) -> list[str]:
974 issues = []
975 for pattern, message in VALIDATION_RULES:
976 if re.search(pattern, command):
977 issues.append(message)
978 return issues
979
980
981try:
982 input_data = json.load(sys.stdin)
983except json.JSONDecodeError as e:
984 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
985 sys.exit(1)
986
987tool_name = input_data.get("tool_name", "")
988tool_input = input_data.get("tool_input", {})
989command = tool_input.get("command", "")
990
991if tool_name != "Bash" or not command:
992 sys.exit(1)
993
994# Validate the command
995issues = validate_command(command)
996
997if issues:
998 for message in issues:
999 print(f"• {message}", file=sys.stderr)
1000 # Exit code 2 blocks tool call and shows stderr to Claude
1001 sys.exit(2)
1002```
1003 1566
1004#### JSON Output Example: UserPromptSubmit to Add Context and Validation1567Agent-based hooks (`type: "agent"`) are like prompt-based hooks but with multi-turn tool access. Instead of a single LLM call, an agent hook spawns a subagent that can read files, search code, and inspect the codebase to verify conditions. Agent hooks support the same events as prompt-based hooks.
1005 1568
1006<Note>1569### How agent hooks work
1007 For `UserPromptSubmit` hooks, you can inject context using either method:
1008 1570
1009 * **Plain text stdout** with exit code 0: Simplest approach, prints text1571When an agent hook fires:
1010 * **JSON output** with exit code 0: Use `"decision": "block"` to reject prompts,
1011 or `additionalContext` for structured context injection
1012 1572
1013 Remember: Exit code 2 only uses `stderr` for the error message. To block using15731. Claude Code spawns a subagent with your prompt and the hook's JSON input
1014 JSON (with a custom reason), use `"decision": "block"` with exit code 0.15742. The subagent can use tools like Read, Grep, and Glob to investigate
1015</Note>15753. After up to 50 turns, the subagent returns a structured `{ "ok": true/false }` decision
15764. Claude Code processes the decision the same way as a prompt hook
1016 1577
1017```python theme={null}1578Agent hooks are useful when verification requires inspecting actual files or test output, not just evaluating the hook input data alone.
1018#!/usr/bin/env python3
1019import json
1020import sys
1021import re
1022import datetime
1023
1024# Load input from stdin
1025try:
1026 input_data = json.load(sys.stdin)
1027except json.JSONDecodeError as e:
1028 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
1029 sys.exit(1)
1030
1031prompt = input_data.get("prompt", "")
1032
1033# Check for sensitive patterns
1034sensitive_patterns = [
1035 (r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),
1036]
1037
1038for pattern, message in sensitive_patterns:
1039 if re.search(pattern, prompt):
1040 # Use JSON output to block with a specific reason
1041 output = {
1042 "decision": "block",
1043 "reason": f"Security policy violation: {message}. Please rephrase your request without sensitive information."
1044 }
1045 print(json.dumps(output))
1046 sys.exit(0)
1047 1579
1048# Add current time to context1580### Agent hook configuration
1049context = f"Current time: {datetime.datetime.now()}"
1050print(context)
1051 1581
1052"""1582Set `type` to `"agent"` and provide a `prompt` string. The configuration fields are the same as [prompt hooks](#prompt-hook-configuration), with a longer default timeout:
1053The following is also equivalent:
1054print(json.dumps({
1055 "hookSpecificOutput": {
1056 "hookEventName": "UserPromptSubmit",
1057 "additionalContext": context,
1058 },
1059}))
1060"""
1061 1583
1062# Allow the prompt to proceed with the additional context1584| Field | Required | Description |
1063sys.exit(0)1585| :-------- | :------- | :------------------------------------------------------------------------------------------ |
1064```1586| `type` | yes | Must be `"agent"` |
1587| `prompt` | yes | Prompt describing what to verify. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
1588| `model` | no | Model to use. Defaults to a fast model |
1589| `timeout` | no | Timeout in seconds. Default: 60 |
1065 1590
1066#### JSON Output Example: PreToolUse with Approval1591The response schema is the same as prompt hooks: `{ "ok": true }` to allow or `{ "ok": false, "reason": "..." }` to block.
1067
1068```python theme={null}
1069#!/usr/bin/env python3
1070import json
1071import sys
1072
1073# Load input from stdin
1074try:
1075 input_data = json.load(sys.stdin)
1076except json.JSONDecodeError as e:
1077 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
1078 sys.exit(1)
1079
1080tool_name = input_data.get("tool_name", "")
1081tool_input = input_data.get("tool_input", {})
1082
1083# Example: Auto-approve file reads for documentation files
1084if tool_name == "Read":
1085 file_path = tool_input.get("file_path", "")
1086 if file_path.endswith((".md", ".mdx", ".txt", ".json")):
1087 # Use JSON output to auto-approve the tool call
1088 output = {
1089 "decision": "approve",
1090 "reason": "Documentation file auto-approved",
1091 "suppressOutput": True # Don't show in verbose mode
1092 }
1093 print(json.dumps(output))
1094 sys.exit(0)
1095
1096# For other cases, let the normal permission flow proceed
1097sys.exit(0)
1098```
1099 1592
1100## Working with MCP Tools1593This `Stop` hook verifies that all unit tests pass before allowing Claude to finish:
1101 1594
1102Claude Code hooks work seamlessly with1595```json theme={null}
1103[Model Context Protocol (MCP) tools](/en/mcp). When MCP servers1596{
1104provide tools, they appear with a special naming pattern that you can match in1597 "hooks": {
1105your hooks.1598 "Stop": [
1599 {
1600 "hooks": [
1601 {
1602 "type": "agent",
1603 "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
1604 "timeout": 120
1605 }
1606 ]
1607 }
1608 ]
1609 }
1610}
1611```
1106 1612
1107### MCP Tool Naming1613## Run hooks in the background
1108 1614
1109MCP tools follow the pattern `mcp__<server>__<tool>`, for example:1615By default, hooks block Claude's execution until they complete. For long-running tasks like deployments, test suites, or external API calls, set `"async": true` to run the hook in the background while Claude continues working. Async hooks cannot block or control Claude's behavior: response fields like `decision`, `permissionDecision`, and `continue` have no effect, because the action they would have controlled has already completed.
1110 1616
1111* `mcp__memory__create_entities` - Memory server's create entities tool1617### Configure an async hook
1112* `mcp__filesystem__read_file` - Filesystem server's read file tool
1113* `mcp__github__search_repositories` - GitHub server's search tool
1114 1618
1115### Configuring Hooks for MCP Tools1619Add `"async": true` to a command hook's configuration to run it in the background without blocking Claude. This field is only available on `type: "command"` hooks.
1116 1620
1117You can target specific MCP tools or entire MCP servers:1621This hook runs a test script after every `Write` tool call. Claude continues working immediately while `run-tests.sh` executes for up to 120 seconds. When the script finishes, its output is delivered on the next conversation turn:
1118 1622
1119```json theme={null}1623```json theme={null}
1120{1624{
1121 "hooks": {1625 "hooks": {
1122 "PreToolUse": [1626 "PostToolUse": [
1123 {
1124 "matcher": "mcp__memory__.*",
1125 "hooks": [
1126 {
1127 "type": "command",
1128 "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
1129 }
1130 ]
1131 },
1132 {1627 {
1133 "matcher": "mcp__.*__write.*",1628 "matcher": "Write",
1134 "hooks": [1629 "hooks": [
1135 {1630 {
1136 "type": "command",1631 "type": "command",
1137 "command": "/home/user/scripts/validate-mcp-write.py"1632 "command": "/path/to/run-tests.sh",
1633 "async": true,
1634 "timeout": 120
1138 }1635 }
1139 ]1636 ]
1140 }1637 }
1143}1640}
1144```1641```
1145 1642
1146## Examples1643The `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.
1147
1148<Tip>
1149 For practical examples including code formatting, notifications, and file protection, see [More Examples](/en/hooks-guide#more-examples) in the get started guide.
1150</Tip>
1151
1152## Security Considerations
1153 1644
1154### Disclaimer1645### How async hooks execute
1155 1646
1156**USE AT YOUR OWN RISK**: Claude Code hooks execute arbitrary shell commands on1647When an async hook fires, Claude Code starts the hook process and immediately continues without waiting for it to finish. The hook receives the same JSON input via stdin as a synchronous hook.
1157your system automatically. By using hooks, you acknowledge that:
1158 1648
1159* You are solely responsible for the commands you configure1649After the background process exits, if the hook produced a JSON response with a `systemMessage` or `additionalContext` field, that content is delivered to Claude as context on the next conversation turn.
1160* Hooks can modify, delete, or access any files your user account can access
1161* Malicious or poorly written hooks can cause data loss or system damage
1162* Anthropic provides no warranty and assumes no liability for any damages
1163 resulting from hook usage
1164* You should thoroughly test hooks in a safe environment before production use
1165 1650
1166Always review and understand any hook commands before adding them to your1651### Example: run tests after file changes
1167configuration.
1168 1652
1169### Security Best Practices1653This hook starts a test suite in the background whenever Claude writes a file, then reports the results back to Claude when the tests finish. Save this script to `.claude/hooks/run-tests-async.sh` in your project and make it executable with `chmod +x`:
1170 1654
1171Here are some key practices for writing more secure hooks:1655```bash theme={null}
1656#!/bin/bash
1657# run-tests-async.sh
1172 1658
11731. **Validate and sanitize inputs** - Never trust input data blindly1659# Read hook input from stdin
11742. **Always quote shell variables** - Use `"$VAR"` not `$VAR`1660INPUT=$(cat)
11753. **Block path traversal** - Check for `..` in file paths1661FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
11764. **Use absolute paths** - Specify full paths for scripts (use
1177 "\$CLAUDE\_PROJECT\_DIR" for the project path)
11785. **Skip sensitive files** - Avoid `.env`, `.git/`, keys, etc.
1179 1662
1180### Configuration Safety1663# Only run tests for source files
1664if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
1665 exit 0
1666fi
1181 1667
1182Direct edits to hooks in settings files don't take effect immediately. Claude1668# Run tests and report results via systemMessage
1183Code:1669RESULT=$(npm test 2>&1)
1670EXIT_CODE=$?
1184 1671
11851. Captures a snapshot of hooks at startup1672if [ $EXIT_CODE -eq 0 ]; then
11862. Uses this snapshot throughout the session1673 echo "{\"systemMessage\": \"Tests passed after editing $FILE_PATH\"}"
11873. Warns if hooks are modified externally1674else
11884. Requires review in `/hooks` menu for changes to apply1675 echo "{\"systemMessage\": \"Tests failed after editing $FILE_PATH: $RESULT\"}"
1676fi
1677```
1189 1678
1190This prevents malicious hook modifications from affecting your current session.1679Then add this configuration to `.claude/settings.json` in your project root. The `async: true` flag lets Claude keep working while tests run:
1191 1680
1192## Hook Execution Details1681```json theme={null}
1682{
1683 "hooks": {
1684 "PostToolUse": [
1685 {
1686 "matcher": "Write|Edit",
1687 "hooks": [
1688 {
1689 "type": "command",
1690 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests-async.sh",
1691 "async": true,
1692 "timeout": 300
1693 }
1694 ]
1695 }
1696 ]
1697 }
1698}
1699```
1193 1700
1194* **Timeout**: 60-second execution limit by default, configurable per command.1701### Limitations
1195 * A timeout for an individual command does not affect the other commands.
1196* **Parallelization**: All matching hooks run in parallel
1197* **Deduplication**: Multiple identical hook commands are deduplicated automatically
1198* **Environment**: Runs in current directory with Claude Code's environment
1199 * The `CLAUDE_PROJECT_DIR` environment variable is available and contains the
1200 absolute path to the project root directory (where Claude Code was started)
1201 * The `CLAUDE_CODE_REMOTE` environment variable indicates whether the hook is running in a remote (web) environment (`"true"`) or local CLI environment (not set or empty). Use this to run different logic based on execution context.
1202* **Input**: JSON via stdin
1203* **Output**:
1204 * PreToolUse/PermissionRequest/PostToolUse/Stop/SubagentStop: Progress shown in verbose mode (ctrl+o)
1205 * Notification/SessionEnd: Logged to debug only (`--debug`)
1206 * UserPromptSubmit/SessionStart: stdout added as context for Claude
1207 1702
1208## Debugging1703Async hooks have several constraints compared to synchronous hooks:
1209 1704
1210### Basic Troubleshooting1705* Only `type: "command"` hooks support `async`. Prompt-based hooks cannot run asynchronously.
1706* Async hooks cannot block tool calls or return decisions. By the time the hook completes, the triggering action has already proceeded.
1707* Hook output is delivered on the next conversation turn. If the session is idle, the response waits until the next user interaction.
1708* Each execution creates a separate background process. There is no deduplication across multiple firings of the same async hook.
1211 1709
1212If your hooks aren't working:1710## Security considerations
1213 1711
12141. **Check configuration** - Run `/hooks` to see if your hook is registered1712### Disclaimer
12152. **Verify syntax** - Ensure your JSON settings are valid
12163. **Test commands** - Run hook commands manually first
12174. **Check permissions** - Make sure scripts are executable
12185. **Review logs** - Use `claude --debug` to see hook execution details
1219 1713
1220Common issues:1714Hooks run with your system user's full permissions.
1221 1715
1222* **Quotes not escaped** - Use `\"` inside JSON strings1716<Warning>
1223* **Wrong matcher** - Check tool names match exactly (case-sensitive)1717 Hooks execute shell commands with your full user permissions. They can modify, delete, or access any files your user account can access. Review and test all hook commands before adding them to your configuration.
1224* **Command not found** - Use full paths for scripts1718</Warning>
1225 1719
1226### Advanced Debugging1720### Security best practices
1227 1721
1228For complex hook issues:1722Keep these practices in mind when writing hooks:
1229 1723
12301. **Inspect hook execution** - Use `claude --debug` to see detailed hook1724* **Validate and sanitize inputs**: never trust input data blindly
1231 execution1725* **Always quote shell variables**: use `"$VAR"` not `$VAR`
12322. **Validate JSON schemas** - Test hook input/output with external tools1726* **Block path traversal**: check for `..` in file paths
12333. **Check environment variables** - Verify Claude Code's environment is correct1727* **Use absolute paths**: specify full paths for scripts, using `"$CLAUDE_PROJECT_DIR"` for the project root
12344. **Test edge cases** - Try hooks with unusual file paths or inputs1728* **Skip sensitive files**: avoid `.env`, `.git/`, keys, etc.
12355. **Monitor system resources** - Check for resource exhaustion during hook
1236 execution
12376. **Use structured logging** - Implement logging in your hook scripts
1238 1729
1239### Debug Output Example1730## Debug hooks
1240 1731
1241Use `claude --debug` to see hook execution details:1732Run `claude --debug` to see hook execution details, including which hooks matched, their exit codes, and output. Toggle verbose mode with `Ctrl+O` to see hook progress in the transcript.
1242 1733
1243```1734```
1244[DEBUG] Executing hooks for PostToolUse:Write1735[DEBUG] Executing hooks for PostToolUse:Write
1246[DEBUG] Found 1 hook matchers in settings1737[DEBUG] Found 1 hook matchers in settings
1247[DEBUG] Matched 1 hooks for query "Write"1738[DEBUG] Matched 1 hooks for query "Write"
1248[DEBUG] Found 1 hook commands to execute1739[DEBUG] Found 1 hook commands to execute
1249[DEBUG] Executing hook command: <Your command> with timeout 60000ms1740[DEBUG] Executing hook command: <Your command> with timeout 600000ms
1250[DEBUG] Hook command completed with status 0: <Your stdout>1741[DEBUG] Hook command completed with status 0: <Your stdout>
1251```1742```
1252 1743
1253Progress messages appear in verbose mode (ctrl+o) showing:1744For troubleshooting common issues like hooks not firing, infinite Stop hook loops, or configuration errors, see [Limitations and troubleshooting](/en/hooks-guide#limitations-and-troubleshooting) in the guide.
1254
1255* Which hook is running
1256* Command being executed
1257* Success/failure status
1258* Output or error messages
1259
1260
1261
1262> To find navigation and other pages in this documentation, fetch the llms.txt file at: https://code.claude.com/docs/llms.txt