4 4
5# Hooks reference5# Hooks reference
6 6
7> 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.
8 8
9<Tip>9<Tip>
10 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).
11</Tip>11</Tip>
12 12
13Hooks are user-defined shell commands or LLM prompts that execute automatically at specific points in Claude Code's lifecycle. Use this reference to look up event schemas, configuration options, JSON input/output formats, and advanced features like async hooks and MCP tool hooks. If you're setting up hooks for the first time, start with the [guide](/en/hooks-guide) instead.
14
13## Hook lifecycle15## Hook lifecycle
14 16
15Hooks fire at specific points during a Claude Code session.17Hooks fire at specific points during a Claude Code session. When an event fires and a matcher matches, Claude Code passes JSON context about the event to your hook handler. For command hooks, this arrives on stdin. Your handler can then inspect the input, take action, and optionally return a decision. Some events fire once per session, while others fire repeatedly inside the agentic loop:
16 18
17<div style={{maxWidth: "500px", margin: "0 auto"}}>19<div style={{maxWidth: "500px", margin: "0 auto"}}>
18 <Frame>20 <Frame>
20 </Frame>22 </Frame>
21</div>23</div>
22 24
23| Hook | When it fires |25The table below summarizes when each event fires. The [Hook events](#hook-events) section documents the full input schema and decision control options for each one.
24| :------------------- | :------------------------------ |26
25| `SessionStart` | Session begins or resumes |27| Event | When it fires |
26| `UserPromptSubmit` | User submits a prompt |28| :------------------- | :--------------------------------------------------- |
27| `PreToolUse` | Before tool execution |29| `SessionStart` | When a session begins or resumes |
28| `PermissionRequest` | When permission dialog appears |30| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |
29| `PostToolUse` | After tool succeeds |31| `PreToolUse` | Before a tool call executes. Can block it |
30| `PostToolUseFailure` | After tool fails |32| `PermissionRequest` | When a permission dialog appears |
31| `SubagentStart` | When spawning a subagent |33| `PostToolUse` | After a tool call succeeds |
32| `SubagentStop` | When subagent finishes |34| `PostToolUseFailure` | After a tool call fails |
33| `Stop` | Claude finishes responding |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 |
34| `PreCompact` | Before context compaction |39| `PreCompact` | Before context compaction |
35| `SessionEnd` | Session terminates |40| `SessionEnd` | When a session terminates |
36| `Notification` | Claude Code sends notifications |41
42### How a hook resolves
43
44To see how these pieces fit together, consider this `PreToolUse` hook that blocks destructive shell commands. The hook runs `block-rm.sh` before every Bash tool call:
45
46```json theme={null}
47{
48 "hooks": {
49 "PreToolUse": [
50 {
51 "matcher": "Bash",
52 "hooks": [
53 {
54 "type": "command",
55 "command": ".claude/hooks/block-rm.sh"
56 }
57 ]
58 }
59 ]
60 }
61}
62```
63
64The script reads the JSON input from stdin, extracts the command, and blocks it if it contains `rm -rf`:
65
66```bash theme={null}
67#!/bin/bash
68# .claude/hooks/block-rm.sh
69COMMAND=$(jq -r '.tool_input.command')
70
71if echo "$COMMAND" | grep -q 'rm -rf'; then
72 echo '{"decision":"block","reason":"Destructive command blocked by hook"}'
73else
74 exit 0 # allow the command
75fi
76```
77
78Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:
79
80<Frame>
81 <img src="https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=7c13f51ffcbc37d22a593b27e2f2de72" alt="Hook resolution flow: PreToolUse event fires, matcher checks for Bash match, hook handler runs, result returns to Claude Code" data-og-width="780" width="780" data-og-height="290" height="290" data-path="images/hook-resolution.svg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=280&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=36a39a07e8bc1995dcb4639e09846905 280w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=560&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=6568d90c596c7605bbac2c325b0a0c86 560w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=840&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=255a6f68b9475a0e41dbde7b88002dad 840w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=1100&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=dcecf8d5edc88cd2bc49deb006d5760d 1100w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=1650&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=04fe51bf69ae375e9fd517f18674e35f 1650w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=2500&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=b1b76e0b77fddb5c7fa7bf302dacd80b 2500w" />
82</Frame>
83
84<Steps>
85 <Step title="Event fires">
86 The `PreToolUse` event fires. Claude Code sends the tool input as JSON on stdin to the hook:
87
88 ```json theme={null}
89 { "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
90 ```
91 </Step>
92
93 <Step title="Matcher checks">
94 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.
95 </Step>
96
97 <Step title="Hook handler runs">
98 The script extracts `"rm -rf /tmp/build"` from the input and finds `rm -rf`, so it prints a decision to stdout:
99
100 ```json theme={null}
101 { "decision": "block", "reason": "Destructive command blocked by hook" }
102 ```
103
104 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.
105 </Step>
106
107 <Step title="Claude Code acts on the result">
108 Claude Code reads the JSON decision, blocks the tool call, and shows Claude the reason.
109 </Step>
110</Steps>
111
112The [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.
37 113
38## Configuration114## Configuration
39 115
40Claude Code hooks are configured in your [settings files](/en/settings):116Hooks are defined in JSON settings files. The configuration has three levels of nesting:
41 117
42* `~/.claude/settings.json` - User settings1181. Choose a [hook event](#hook-events) to respond to, like `PreToolUse` or `Stop`
43* `.claude/settings.json` - Project settings1192. Add a [matcher group](#matcher-patterns) to filter when it fires, like "only for the Bash tool"
44* `.claude/settings.local.json` - Local project settings (not committed)1203. Define one or more [hook handlers](#hook-handler-fields) to run when matched
45* Managed policy settings121
122See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.
46 123
47<Note>124<Note>
48 Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. See [Hook configuration](/en/settings#hook-configuration).125 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.
49</Note>126</Note>
50 127
51### Structure128### Hook locations
129
130Where you define a hook determines its scope:
131
132| Location | Scope | Shareable |
133| :--------------------------------------------------------- | :---------------------------- | :--------------------------------- |
134| `~/.claude/settings.json` | All your projects | No, local to your machine |
135| `.claude/settings.json` | Single project | Yes, can be committed to the repo |
136| `.claude/settings.local.json` | Single project | No, gitignored |
137| Managed policy settings | Organization-wide | Yes, admin-controlled |
138| [Plugin](/en/plugins) `hooks/hooks.json` | When plugin is enabled | Yes, bundled with the plugin |
139| [Skill](/en/skills) or [agent](/en/sub-agents) frontmatter | While the component is active | Yes, defined in the component file |
140
141For 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).
52 142
53Hooks are organized by matchers, where each matcher can have multiple hooks:143### Matcher patterns
144
145The `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:
146
147| Event | What the matcher filters | Example matcher values |
148| :--------------------------------------------------------------------- | :------------------------ | :----------------------------------------------------------------------------- |
149| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |
150| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |
151| `SessionEnd` | why the session ended | `clear`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |
152| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |
153| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |
154| `PreCompact` | what triggered compaction | `manual`, `auto` |
155| `SubagentStop` | agent type | same values as `SubagentStart` |
156| `UserPromptSubmit`, `Stop` | no matcher support | always fires on every occurrence |
157
158The 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.
159
160This example runs a linting script only when Claude writes or edits a file:
54 161
55```json theme={null}162```json theme={null}
56{163{
57 "hooks": {164 "hooks": {
58 "EventName": [165 "PostToolUse": [
59 {166 {
60 "matcher": "ToolPattern",167 "matcher": "Edit|Write",
61 "hooks": [168 "hooks": [
62 {169 {
63 "type": "command",170 "type": "command",
64 "command": "your-command-here"171 "command": "/path/to/lint-check.sh"
65 }172 }
66 ]173 ]
67 }174 }
70}177}
71```178```
72 179
73* **matcher**: Pattern to match tool names, case-sensitive (only applicable for180`UserPromptSubmit` and `Stop` don't support matchers and always fire on every occurrence. If you add a `matcher` field to these events, it is silently ignored.
74 `PreToolUse`, `PermissionRequest`, and `PostToolUse`)181
75 * Simple strings match exactly: `Write` matches only the Write tool182#### Match MCP tools
76 * Supports regex: `Edit|Write` or `Notebook.*`183
77 * Use `*` to match all tools. You can also use empty string (`""`) or leave184[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.
78 `matcher` blank.185
79* **hooks**: Array of hooks to execute when the pattern matches186MCP tools follow the naming pattern `mcp__<server>__<tool>`, for example:
80 * `type`: Hook execution type - `"command"` for bash commands or `"prompt"` for LLM-based evaluation187
81 * `command`: (For `type: "command"`) The bash command to execute (can use `$CLAUDE_PROJECT_DIR` environment variable)188* `mcp__memory__create_entities`: Memory server's create entities tool
82 * `prompt`: (For `type: "prompt"`) The prompt to send to the LLM for evaluation189* `mcp__filesystem__read_file`: Filesystem server's read file tool
83 * `timeout`: (Optional) How long a hook should run, in seconds, before canceling that specific hook190* `mcp__github__search_repositories`: GitHub server's search tool
84 191
85For events like `UserPromptSubmit`, `Stop`, `SubagentStop`, and `Setup`192Use regex patterns to target specific MCP tools or groups of tools:
86that don't use matchers, you can omit the matcher field:193
194* `mcp__memory__.*` matches all tools from the `memory` server
195* `mcp__.*__write.*` matches any tool containing "write" from any server
196
197This example logs all memory server operations and validates write operations from any MCP server:
87 198
88```json theme={null}199```json theme={null}
89{200{
90 "hooks": {201 "hooks": {
91 "UserPromptSubmit": [202 "PreToolUse": [
203 {
204 "matcher": "mcp__memory__.*",
205 "hooks": [
92 {206 {
207 "type": "command",
208 "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
209 }
210 ]
211 },
212 {
213 "matcher": "mcp__.*__write.*",
93 "hooks": [214 "hooks": [
94 {215 {
95 "type": "command",216 "type": "command",
96 "command": "/path/to/prompt-validator.py"217 "command": "/home/user/scripts/validate-mcp-write.py"
97 }218 }
98 ]219 ]
99 }220 }
102}223}
103```224```
104 225
105### Project-Specific Hook Scripts226### Hook handler fields
106 227
107You can use the environment variable `CLAUDE_PROJECT_DIR` (only available when228Each 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:
108Claude Code spawns the hook command) to reference scripts stored in your project,
109ensuring they work regardless of Claude's current directory:
110 229
111```json theme={null}230* **[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.
112{231* **[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).
232* **[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).
233
234#### Common fields
235
236These fields apply to all hook types:
237
238| Field | Required | Description |
239| :-------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |
240| `type` | yes | `"command"`, `"prompt"`, or `"agent"` |
241| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |
242| `statusMessage` | no | Custom spinner message displayed while the hook runs |
243| `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) |
244
245#### Command hook fields
246
247In addition to the [common fields](#common-fields), command hooks accept these fields:
248
249| Field | Required | Description |
250| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------ |
251| `command` | yes | Shell command to execute |
252| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |
253
254#### Prompt and agent hook fields
255
256In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:
257
258| Field | Required | Description |
259| :------- | :------- | :------------------------------------------------------------------------------------------ |
260| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
261| `model` | no | Model to use for evaluation. Defaults to a fast model |
262
263All 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.
264
265### Reference scripts by path
266
267Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:
268
269* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.
270* `${CLAUDE_PLUGIN_ROOT}`: the plugin's root directory, for scripts bundled with a [plugin](/en/plugins).
271
272<Tabs>
273 <Tab title="Project scripts">
274 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:
275
276 ```json theme={null}
277 {
113 "hooks": {278 "hooks": {
114 "PostToolUse": [279 "PostToolUse": [
115 {280 {
123 }288 }
124 ]289 ]
125 }290 }
126}291 }
127```292 ```
128 293 </Tab>
129### Plugin hooks
130
131[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.
132
133**How plugin hooks work**:
134 294
135* 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.295 <Tab title="Plugin scripts">
136* When a plugin is enabled, its hooks are merged with user and project hooks296 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.
137* Multiple hooks from different sources can respond to the same event
138* Plugin hooks use the `${CLAUDE_PLUGIN_ROOT}` environment variable to reference plugin files
139 297
140**Example plugin hook configuration**:298 This example runs a formatting script bundled with the plugin:
141 299
142```json theme={null}300 ```json theme={null}
143{301 {
144 "description": "Automatic code formatting",302 "description": "Automatic code formatting",
145 "hooks": {303 "hooks": {
146 "PostToolUse": [304 "PostToolUse": [
156 }314 }
157 ]315 ]
158 }316 }
159}317 }
160```318 ```
161
162<Note>
163 Plugin hooks use the same format as regular hooks with an optional `description` field to explain the hook's purpose.
164</Note>
165
166<Note>
167 Plugin hooks run alongside your custom hooks. If multiple hooks match an event, they all execute in parallel.
168</Note>
169
170**Environment variables for plugins**:
171
172* `${CLAUDE_PLUGIN_ROOT}`: Absolute path to the plugin directory
173* `${CLAUDE_PROJECT_DIR}`: Project root directory (same as for project hooks)
174* All standard environment variables are available
175 319
176See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.320 See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.
321 </Tab>
322</Tabs>
177 323
178### Hooks in skills and agents324### Hooks in skills and agents
179 325
180In 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.326In 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.
181 327
182**Supported events**: `PreToolUse`, `PostToolUse`, and `Stop`328All hook events are supported. For subagents, `Stop` hooks are automatically converted to `SubagentStop` since that is the event that fires when a subagent completes.
183 329
184**Example in a Skill**:330Hooks use the same configuration format as settings-based hooks but are scoped to the component's lifetime and cleaned up when it finishes.
331
332This skill defines a `PreToolUse` hook that runs a security validation script before each `Bash` command:
185 333
186```yaml theme={null}334```yaml theme={null}
187---335---
196---344---
197```345```
198 346
199**Example in an agent**:347Agents use the same format in their YAML frontmatter.
200
201```yaml theme={null}
202name: code-reviewer
203description: Review code changes
204hooks:
205 PostToolUse:
206 - matcher: "Edit|Write"
207 hooks:
208 - type: command
209 command: "./scripts/run-linter.sh"
210```
211
212Component-scoped hooks follow the same configuration format as settings-based hooks but are automatically cleaned up when the component finishes executing.
213 348
214**Additional option for skills:**349### The `/hooks` menu
215
216* `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.
217
218## Prompt-Based Hooks
219
220In 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.
221
222### How prompt-based hooks work
223
224Instead of executing a bash command, prompt-based hooks:
225
2261. Send the hook input and your prompt to a fast LLM (Haiku)
2272. The LLM responds with structured JSON containing a decision
2283. Claude Code processes the decision automatically
229
230### Configuration
231
232```json theme={null}
233{
234 "hooks": {
235 "Stop": [
236 {
237 "hooks": [
238 {
239 "type": "prompt",
240 "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
241 }
242 ]
243 }
244 ]
245 }
246}
247```
248 350
249**Fields:**351Type `/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.
250 352
251* `type`: Must be `"prompt"`353Each hook in the menu is labeled with a bracket prefix indicating its source:
252* `prompt`: The prompt text to send to the LLM
253 * Use `$ARGUMENTS` as a placeholder for the hook input JSON
254 * If `$ARGUMENTS` is not present, input JSON is appended to the prompt
255* `timeout`: (Optional) Timeout in seconds (default: 30 seconds)
256 354
257### Response schema355* `[User]`: from `~/.claude/settings.json`
356* `[Project]`: from `.claude/settings.json`
357* `[Local]`: from `.claude/settings.local.json`
358* `[Plugin]`: from a plugin's `hooks/hooks.json`, read-only
258 359
259The LLM must respond with JSON containing:360### Disable or remove hooks
260 361
261```json theme={null}362To remove a hook, delete its entry from the settings JSON file, or use the `/hooks` menu and select the hook to delete it.
262{
263 "ok": true | false,
264 "reason": "Explanation for the decision"
265}
266```
267 363
268**Response fields:**364To 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.
269 365
270* `ok`: `true` allows the action, `false` prevents it366Direct 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.
271* `reason`: Required when `ok` is `false`. Explanation shown to Claude
272 367
273### Supported hook events368## Hook input and output
274 369
275Prompt-based hooks work with any hook event, but are most useful for:370Hooks 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.
276 371
277* **Stop**: Intelligently decide if Claude should continue working372### Common input fields
278* **SubagentStop**: Evaluate if a subagent has completed its task
279* **UserPromptSubmit**: Validate user prompts with LLM assistance
280* **PreToolUse**: Make context-aware permission decisions
281* **PermissionRequest**: Intelligently allow or deny permission dialogs
282 373
283### Example: Intelligent Stop hook374All hook events receive these fields via stdin as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section:
284 375
285```json theme={null}376| Field | Description |
286{377| :---------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
287 "hooks": {378| `session_id` | Current session identifier |
288 "Stop": [379| `transcript_path` | Path to conversation JSON |
289 {380| `cwd` | Current working directory when the hook is invoked |
290 "hooks": [381| `permission_mode` | Current [permission mode](/en/iam#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"dontAsk"`, or `"bypassPermissions"` |
291 {382| `hook_event_name` | Name of the event that fired |
292 "type": "prompt",
293 "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.",
294 "timeout": 30
295 }
296 ]
297 }
298 ]
299 }
300}
301```
302 383
303### Example: SubagentStop with custom logic384For example, a `PreToolUse` hook for a Bash command receives this on stdin:
304 385
305```json theme={null}386```json theme={null}
306{387{
307 "hooks": {388 "session_id": "abc123",
308 "SubagentStop": [389 "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
309 {390 "cwd": "/home/user/my-project",
310 "hooks": [391 "permission_mode": "default",
311 {392 "hook_event_name": "PreToolUse",
312 "type": "prompt",393 "tool_name": "Bash",
313 "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."394 "tool_input": {
314 }395 "command": "npm test"
315 ]
316 }
317 ]
318 }396 }
319}397}
320```398```
321 399
322### Comparison with bash command hooks400The `tool_name` and `tool_input` fields are event-specific. Each [hook event](#hook-events) section documents the additional fields for that event.
323 401
324| Feature | Bash Command Hooks | Prompt-Based Hooks |402### Exit code output
325| --------------------- | ----------------------- | ------------------------------ |
326| **Execution** | Runs bash script | Queries LLM |
327| **Decision logic** | You implement in code | LLM evaluates context |
328| **Setup complexity** | Requires script file | Configure prompt |
329| **Context awareness** | Limited to script logic | Natural language understanding |
330| **Performance** | Fast (local execution) | Slower (API call) |
331| **Use case** | Deterministic rules | Context-aware decisions |
332 403
333### Best practices404The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.
334 405
335* **Be specific in prompts**: Clearly state what you want the LLM to evaluate406**Exit 0** means success. Claude Code parses stdout for [JSON output fields](#json-output) like `decision` or `reason`. 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.
336* **Include decision criteria**: List the factors the LLM should consider
337* **Test your prompts**: Verify the LLM makes correct decisions for your use cases
338* **Set appropriate timeouts**: Default is 30 seconds, adjust if needed
339* **Use for complex decisions**: Bash hooks are better for simple, deterministic rules
340 407
341See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.408**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.
342 409
343## Hook Events410**Any other exit code** is a non-blocking error. stderr is shown in verbose mode (`Ctrl+O`) and execution continues.
344 411
345### PreToolUse412For example, a hook command script that blocks dangerous Bash commands:
346 413
347Runs after Claude creates tool parameters and before processing the tool call.414```bash theme={null}
415#!/bin/bash
416# Reads JSON input from stdin, checks the command
417command=$(jq -r '.tool_input.command' < /dev/stdin)
348 418
349**Common matchers:**419if [[ "$command" == rm* ]]; then
420 echo "Blocked: rm commands are not allowed" >&2
421 exit 2 # Blocking error: tool call is prevented
422fi
350 423
351* `Task` - Subagent tasks (see [subagents documentation](/en/sub-agents))424exit 0 # Success: tool call proceeds
352* `Bash` - Shell commands425```
353* `Glob` - File pattern matching
354* `Grep` - Content search
355* `Read` - File reading
356* `Edit` - File editing
357* `Write` - File writing
358* `WebFetch`, `WebSearch` - Web operations
359 426
360Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.427#### Exit code 2 behavior per event
361 428
362### PermissionRequest429Exit 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.
363 430
364Runs when the user is shown a permission dialog.431| Hook event | Can block? | What happens on exit 2 |
365Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.432| :------------------- | :--------- | :-------------------------------------------------------- |
433| `PreToolUse` | Yes | Blocks the tool call |
434| `PermissionRequest` | Yes | Denies the permission |
435| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |
436| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |
437| `SubagentStop` | Yes | Prevents the subagent from stopping |
438| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |
439| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |
440| `Notification` | No | Shows stderr to user only |
441| `SubagentStart` | No | Shows stderr to user only |
442| `SessionStart` | No | Shows stderr to user only |
443| `SessionEnd` | No | Shows stderr to user only |
444| `PreCompact` | No | Shows stderr to user only |
366 445
367Recognizes the same matcher values as PreToolUse.446### JSON output
368 447
369### PostToolUse448You 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.
370 449
371Runs immediately after a tool completes successfully.450Instead of relying on exit codes alone, hooks can print JSON to stdout on exit 0. Claude Code reads specific fields from this JSON to decide what to do next.
372 451
373Recognizes the same matcher values as PreToolUse.452Your 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.
374 453
375### Notification454The JSON object has two parts:
376 455
377Runs when Claude Code sends notifications. Supports matchers to filter by notification type.456* **Top-level fields** like `continue` and `decision` work across all events. These are listed in the table below.
457* **`hookSpecificOutput`** is a nested object for event-specific fields like `permissionDecision` or `additionalContext`. It requires a `hookEventName` field set to the event name, like `"PreToolUse"` or `"Stop"`. Each event's decision control section under [Hook events](#hook-events) documents what fields go here.
378 458
379**Common matchers:**459| Field | Default | Description |
460| :--------------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------- |
461| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over event-specific fields like `decision` or `permissionDecision` |
462| `stopReason` | none | Message shown to the user when `continue` is `false`. Not shown to Claude |
463| `suppressOutput` | `false` | If `true`, hides stdout from verbose mode output |
464| `systemMessage` | none | Warning message shown to the user |
380 465
381* `permission_prompt` - Permission requests from Claude Code466This example uses a top-level field to stop Claude:
382* `idle_prompt` - When Claude is waiting for user input (after 60+ seconds of idle time)
383* `auth_success` - Authentication success notifications
384* `elicitation_dialog` - When Claude Code needs input for MCP tool elicitation
385 467
386You can use matchers to run different hooks for different notification types, or omit the matcher to run hooks for all notifications.468```json theme={null}
469{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
470```
387 471
388**Example: Different notifications for different types**472This example uses `hookSpecificOutput` to deny a PreToolUse tool call:
389 473
390```json theme={null}474```json theme={null}
391{475{
392 "hooks": {476 "hookSpecificOutput": {
393 "Notification": [477 "hookEventName": "PreToolUse",
394 {478 "permissionDecision": "deny",
395 "matcher": "permission_prompt",479 "permissionDecisionReason": "Database writes are not allowed"
396 "hooks": [
397 {
398 "type": "command",
399 "command": "/path/to/permission-alert.sh"
400 }
401 ]
402 },
403 {
404 "matcher": "idle_prompt",
405 "hooks": [
406 {
407 "type": "command",
408 "command": "/path/to/idle-notification.sh"
409 }
410 ]
411 }
412 ]
413 }480 }
414}481}
415```482```
416 483
417### UserPromptSubmit484For 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).
418
419Runs when the user submits a prompt, before Claude processes it. This allows you
420to add additional context based on the prompt/conversation, validate prompts, or
421block certain types of prompts.
422
423### Stop
424
425Runs when the main Claude Code agent has finished responding. Does not run if
426the stoppage occurred due to a user interrupt.
427
428### SubagentStop
429
430Runs when a Claude Code subagent (Task tool call) has finished responding.
431 485
432### PreCompact486## Hook events
433
434Runs before Claude Code is about to run a compact operation.
435 487
436**Matchers:**488Each 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.
437 489
438* `manual` - Invoked from `/compact`490### SessionStart
439* `auto` - Invoked from auto-compact (due to full context window)
440 491
441### Setup492Runs 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.
442 493
443Runs when Claude Code is invoked with repository setup and maintenance flags (`--init`, `--init-only`, or `--maintenance`). Use this hook for operations you don't want on every session—such as installing dependencies, running migrations, or periodic maintenance tasks.494SessionStart runs on every session, so keep these hooks fast.
444 495
445<Note>496The matcher value corresponds to how the session was initiated:
446 Use **Setup** hooks for one-time or occasional operations (dependency installation, migrations, cleanup). Use **SessionStart** hooks for things you want on every session (loading context, setting environment variables). Setup hooks require explicit flags because running them automatically would slow down every session start.
447</Note>
448 497
449**Matchers:**498| Matcher | When it fires |
499| :-------- | :------------------------------------- |
500| `startup` | New session |
501| `resume` | `--resume`, `--continue`, or `/resume` |
502| `clear` | `/clear` |
503| `compact` | Auto or manual compaction |
450 504
451* `init` - Invoked from `--init` or `--init-only` flags505#### SessionStart input
452* `maintenance` - Invoked from `--maintenance` flag
453 506
454Setup hooks have access to the `CLAUDE_ENV_FILE` environment variable for persisting environment variables, similar to SessionStart hooks.507In 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.
455 508
456### SessionStart509```json theme={null}
510{
511 "session_id": "abc123",
512 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
513 "cwd": "/Users/...",
514 "permission_mode": "default",
515 "hook_event_name": "SessionStart",
516 "source": "startup",
517 "model": "claude-sonnet-4-5-20250929"
518}
519```
457 520
458Runs when Claude Code starts a new session or resumes an existing session (which521#### SessionStart decision control
459currently does start a new session under the hood). Useful for loading development context like existing issues or recent changes to your codebase, or setting up environment variables.
460 522
461<Note>523Any 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:
462 For one-time operations like installing dependencies or running migrations, use [Setup hooks](#setup) instead. SessionStart runs on every session, so keep these hooks fast.
463</Note>
464 524
465**Matchers:**525| Field | Description |
526| :------------------ | :------------------------------------------------------------------------ |
527| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |
466 528
467* `startup` - Invoked from startup529```json theme={null}
468* `resume` - Invoked from `--resume`, `--continue`, or `/resume`530{
469* `clear` - Invoked from `/clear`531 "hookSpecificOutput": {
470* `compact` - Invoked from auto or manual compact.532 "hookEventName": "SessionStart",
533 "additionalContext": "My additional context here"
534 }
535}
536```
471 537
472#### Persisting environment variables538#### Persist environment variables
473 539
474SessionStart 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.540SessionStart 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.
475 541
476**Example: Setting individual environment variables**542To set individual environment variables, write `export` statements to `CLAUDE_ENV_FILE`. Use append (`>>`) to preserve variables set by other hooks:
477 543
478```bash theme={null}544```bash theme={null}
479#!/bin/bash545#!/bin/bash
480 546
481if [ -n "$CLAUDE_ENV_FILE" ]; then547if [ -n "$CLAUDE_ENV_FILE" ]; then
482 echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"548 echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
483 echo 'export API_KEY=your-api-key' >> "$CLAUDE_ENV_FILE"549 echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
484 echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"550 echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
485fi551fi
486 552
487exit 0553exit 0
488```554```
489 555
490**Example: Persisting all environment changes from the hook**556To capture all environment changes from setup commands, compare the exported variables before and after:
491
492When your setup modifies the environment (for example, `nvm use`), capture and persist all changes by diffing the environment:
493 557
494```bash theme={null}558```bash theme={null}
495#!/bin/bash559#!/bin/bash
510exit 0572exit 0
511```573```
512 574
513Any variables written to this file will be available in all subsequent bash commands that Claude Code executes during the session.575Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.
514 576
515<Note>577<Note>
516 `CLAUDE_ENV_FILE` is only available for SessionStart hooks. Other hook types do not have access to this variable.578 `CLAUDE_ENV_FILE` is available for SessionStart hooks. Other hook types do not have access to this variable.
517</Note>579</Note>
518 580
519### SessionEnd581### UserPromptSubmit
520
521Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
522statistics, or saving session state.
523
524The `reason` field in the hook input will be one of:
525 582
526* `clear` - Session cleared with /clear command583Runs when the user submits a prompt, before Claude processes it. This allows you
527* `logout` - User logged out584to add additional context based on the prompt/conversation, validate prompts, or
528* `prompt_input_exit` - User exited while prompt input was visible585block certain types of prompts.
529* `other` - Other exit reasons
530 586
531## Hook Input587#### UserPromptSubmit input
532 588
533Hooks receive JSON data via stdin containing session information and589In addition to the [common input fields](#common-input-fields), UserPromptSubmit hooks receive the `prompt` field containing the text the user submitted.
534event-specific data:
535 590
536```typescript theme={null}591```json theme={null}
537{592{
538 // Common fields593 "session_id": "abc123",
539 session_id: string594 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
540 transcript_path: string // Path to conversation JSON595 "cwd": "/Users/...",
541 cwd: string // The current working directory when the hook is invoked596 "permission_mode": "default",
542 permission_mode: string // Current permission mode: "default", "plan", "acceptEdits", "dontAsk", or "bypassPermissions"597 "hook_event_name": "UserPromptSubmit",
543 598 "prompt": "Write a function to calculate the factorial of a number"
544 // Event-specific fields
545 hook_event_name: string
546 ...
547}599}
548```600```
549 601
550### PreToolUse Input602#### UserPromptSubmit decision control
603
604`UserPromptSubmit` hooks can control whether a user prompt is processed and add context. All [JSON output fields](#json-output) are available.
605
606There are two ways to add context to the conversation on exit code 0:
607
608* **Plain text stdout**: any non-JSON text written to stdout is added as context
609* **JSON with `additionalContext`**: use the JSON format below for more control. The `additionalContext` field is added as context
551 610
552The exact schema for `tool_input` depends on the tool. Here are examples for commonly hooked tools.611Plain stdout is shown as hook output in the transcript. The `additionalContext` field is added more discretely.
553 612
554#### Bash tool613To block a prompt, return a JSON object with `decision` set to `"block"`:
555 614
556The Bash tool is the most commonly hooked tool for command validation:615| Field | Description |
616| :------------------ | :----------------------------------------------------------------------------------------------------------------- |
617| `decision` | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |
618| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |
619| `additionalContext` | String added to Claude's context |
557 620
558```json theme={null}621```json theme={null}
559{622{
560 "session_id": "abc123",623 "decision": "block",
561 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",624 "reason": "Explanation for decision",
562 "cwd": "/Users/...",625 "hookSpecificOutput": {
563 "permission_mode": "default",626 "hookEventName": "UserPromptSubmit",
564 "hook_event_name": "PreToolUse",627 "additionalContext": "My additional context here"
565 "tool_name": "Bash",628 }
566 "tool_input": {
567 "command": "psql -c 'SELECT * FROM users'",
568 "description": "Query the users table",
569 "timeout": 120000
570 },
571 "tool_use_id": "toolu_01ABC123..."
572}629}
573```630```
574 631
575| Field | Type | Description |632<Note>
576| :------------------ | :------ | :-------------------------------------------- |633 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
577| `command` | string | The shell command to execute |634 block prompts or want more structured control.
578| `description` | string | Optional description of what the command does |635</Note>
579| `timeout` | number | Optional timeout in milliseconds |636
580| `run_in_background` | boolean | Whether to run the command in background |637### PreToolUse
638
639Runs 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).
640
641Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.
642
643#### PreToolUse input
644
645In 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:
646
647##### Bash
648
649Executes shell commands.
650
651| Field | Type | Example | Description |
652| :------------------ | :------ | :----------------- | :-------------------------------------------- |
653| `command` | string | `"npm test"` | The shell command to execute |
654| `description` | string | `"Run test suite"` | Optional description of what the command does |
655| `timeout` | number | `120000` | Optional timeout in milliseconds |
656| `run_in_background` | boolean | `false` | Whether to run the command in background |
657
658##### Write
659
660Creates or overwrites a file.
661
662| Field | Type | Example | Description |
663| :---------- | :----- | :-------------------- | :--------------------------------- |
664| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to write |
665| `content` | string | `"file content"` | Content to write to the file |
666
667##### Edit
668
669Replaces a string in an existing file.
670
671| Field | Type | Example | Description |
672| :------------ | :------ | :-------------------- | :--------------------------------- |
673| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to edit |
674| `old_string` | string | `"original text"` | Text to find and replace |
675| `new_string` | string | `"replacement text"` | Replacement text |
676| `replace_all` | boolean | `false` | Whether to replace all occurrences |
677
678##### Read
679
680Reads file contents.
581 681
582#### Write tool682| Field | Type | Example | Description |
683| :---------- | :----- | :-------------------- | :----------------------------------------- |
684| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to read |
685| `offset` | number | `10` | Optional line number to start reading from |
686| `limit` | number | `50` | Optional number of lines to read |
687
688##### Glob
689
690Finds files matching a glob pattern.
691
692| Field | Type | Example | Description |
693| :-------- | :----- | :--------------- | :--------------------------------------------------------------------- |
694| `pattern` | string | `"**/*.ts"` | Glob pattern to match files against |
695| `path` | string | `"/path/to/dir"` | Optional directory to search in. Defaults to current working directory |
696
697##### Grep
698
699Searches file contents with regular expressions.
700
701| Field | Type | Example | Description |
702| :------------ | :------ | :--------------- | :------------------------------------------------------------------------------------ |
703| `pattern` | string | `"TODO.*fix"` | Regular expression pattern to search for |
704| `path` | string | `"/path/to/dir"` | Optional file or directory to search in |
705| `glob` | string | `"*.ts"` | Optional glob pattern to filter files |
706| `output_mode` | string | `"content"` | `"content"`, `"files_with_matches"`, or `"count"`. Defaults to `"files_with_matches"` |
707| `-i` | boolean | `true` | Case insensitive search |
708| `multiline` | boolean | `false` | Enable multiline matching |
709
710##### WebFetch
711
712Fetches and processes web content.
713
714| Field | Type | Example | Description |
715| :------- | :----- | :---------------------------- | :----------------------------------- |
716| `url` | string | `"https://example.com/api"` | URL to fetch content from |
717| `prompt` | string | `"Extract the API endpoints"` | Prompt to run on the fetched content |
718
719##### WebSearch
720
721Searches the web.
722
723| Field | Type | Example | Description |
724| :---------------- | :----- | :----------------------------- | :------------------------------------------------ |
725| `query` | string | `"react hooks best practices"` | Search query |
726| `allowed_domains` | array | `["docs.example.com"]` | Optional: only include results from these domains |
727| `blocked_domains` | array | `["spam.example.com"]` | Optional: exclude results from these domains |
728
729##### Task
730
731Spawns a [subagent](/en/sub-agents).
732
733| Field | Type | Example | Description |
734| :-------------- | :----- | :------------------------- | :------------------------------------------- |
735| `prompt` | string | `"Find all API endpoints"` | The task for the agent to perform |
736| `description` | string | `"Find API endpoints"` | Short description of the task |
737| `subagent_type` | string | `"Explore"` | Type of specialized agent to use |
738| `model` | string | `"sonnet"` | Optional model alias to override the default |
739
740#### PreToolUse decision control
741
742`PreToolUse` hooks can control whether a tool call proceeds. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return a `hookSpecificOutput` object with these event-specific fields:
743
744| Field | Description |
745| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
746| `permissionDecision` | `"allow"` bypasses the permission system, `"deny"` prevents the tool call, `"ask"` prompts the user to confirm |
747| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude |
748| `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 |
749| `additionalContext` | String added to Claude's context before the tool executes |
583 750
584```json theme={null}751```json theme={null}
585{752{
586 "session_id": "abc123",753 "hookSpecificOutput": {
587 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",754 "hookEventName": "PreToolUse",
588 "cwd": "/Users/...",755 "permissionDecision": "allow",
589 "permission_mode": "default",756 "permissionDecisionReason": "My reason here",
590 "hook_event_name": "PreToolUse",757 "updatedInput": {
591 "tool_name": "Write",758 "field_to_modify": "new value"
592 "tool_input": {
593 "file_path": "/path/to/file.txt",
594 "content": "file content"
595 },759 },
596 "tool_use_id": "toolu_01ABC123..."760 "additionalContext": "Current environment: production. Proceed with caution."
761 }
597}762}
598```763```
599 764
600| Field | Type | Description |765<Note>
601| :---------- | :----- | :--------------------------------- |766 The `decision` and `reason` fields are deprecated for PreToolUse hooks.
602| `file_path` | string | Absolute path to the file to write |767 Use `hookSpecificOutput.permissionDecision` and
603| `content` | string | Content to write to the file |768 `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields
769 `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.
770</Note>
771
772### PermissionRequest
773
774Runs when the user is shown a permission dialog.
775Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.
776
777Matches on tool name, same values as PreToolUse.
604 778
605#### Edit tool779#### PermissionRequest input
780
781PermissionRequest 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.
606 782
607```json theme={null}783```json theme={null}
608{784{
610 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",786 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
611 "cwd": "/Users/...",787 "cwd": "/Users/...",
612 "permission_mode": "default",788 "permission_mode": "default",
613 "hook_event_name": "PreToolUse",789 "hook_event_name": "PermissionRequest",
614 "tool_name": "Edit",790 "tool_name": "Bash",
615 "tool_input": {791 "tool_input": {
616 "file_path": "/path/to/file.txt",792 "command": "rm -rf node_modules",
617 "old_string": "original text",793 "description": "Remove node_modules directory"
618 "new_string": "replacement text"
619 },794 },
620 "tool_use_id": "toolu_01ABC123..."795 "permission_suggestions": [
796 { "type": "toolAlwaysAllow", "tool": "Bash" }
797 ]
621}798}
622```799```
623 800
624| Field | Type | Description |801#### PermissionRequest decision control
625| :------------ | :------ | :-------------------------------------------------- |
626| `file_path` | string | Absolute path to the file to edit |
627| `old_string` | string | Text to find and replace |
628| `new_string` | string | Replacement text |
629| `replace_all` | boolean | Whether to replace all occurrences (default: false) |
630 802
631#### Read tool803`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:
804
805| Field | Description |
806| :------------------- | :------------------------------------------------------------------------------------------------------------- |
807| `behavior` | `"allow"` grants the permission, `"deny"` denies it |
808| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution |
809| `updatedPermissions` | For `"allow"` only: applies permission rule updates, equivalent to the user selecting an "always allow" option |
810| `message` | For `"deny"` only: tells Claude why the permission was denied |
811| `interrupt` | For `"deny"` only: if `true`, stops Claude |
632 812
633```json theme={null}813```json theme={null}
634{814{
635 "session_id": "abc123",815 "hookSpecificOutput": {
636 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",816 "hookEventName": "PermissionRequest",
637 "cwd": "/Users/...",817 "decision": {
638 "permission_mode": "default",818 "behavior": "allow",
639 "hook_event_name": "PreToolUse",819 "updatedInput": {
640 "tool_name": "Read",820 "command": "npm run lint"
641 "tool_input": {821 }
642 "file_path": "/path/to/file.txt"822 }
643 },823 }
644 "tool_use_id": "toolu_01ABC123..."
645}824}
646```825```
647 826
648| Field | Type | Description |827### PostToolUse
649| :---------- | :----- | :----------------------------------------- |828
650| `file_path` | string | Absolute path to the file to read |829Runs immediately after a tool completes successfully.
651| `offset` | number | Optional line number to start reading from |830
652| `limit` | number | Optional number of lines to read |831Matches on tool name, same values as PreToolUse.
653 832
654### PostToolUse Input833#### PostToolUse input
655 834
656The exact schema for `tool_input` and `tool_response` depends on the tool.835`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.
657 836
658```json theme={null}837```json theme={null}
659{838{
675}854}
676```855```
677 856
678### Notification Input857#### PostToolUse decision control
858
859`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:
860
861| Field | Description |
862| :--------------------- | :----------------------------------------------------------------------------------------- |
863| `decision` | `"block"` prompts Claude with the `reason`. Omit to allow the action to proceed |
864| `reason` | Explanation shown to Claude when `decision` is `"block"` |
865| `additionalContext` | Additional context for Claude to consider |
866| `updatedMCPToolOutput` | For [MCP tools](#match-mcp-tools) only: replaces the tool's output with the provided value |
679 867
680```json theme={null}868```json theme={null}
681{869{
682 "session_id": "abc123",870 "decision": "block",
683 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",871 "reason": "Explanation for decision",
684 "cwd": "/Users/...",872 "hookSpecificOutput": {
685 "permission_mode": "default",873 "hookEventName": "PostToolUse",
686 "hook_event_name": "Notification",874 "additionalContext": "Additional information for Claude"
687 "message": "Claude needs your permission to use Bash",875 }
688 "notification_type": "permission_prompt"
689}876}
690```877```
691 878
692### UserPromptSubmit Input879### PostToolUseFailure
880
881Runs 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.
882
883Matches on tool name, same values as PreToolUse.
884
885#### PostToolUseFailure input
886
887PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as PostToolUse, along with error information as top-level fields:
693 888
694```json theme={null}889```json theme={null}
695{890{
697 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",892 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
698 "cwd": "/Users/...",893 "cwd": "/Users/...",
699 "permission_mode": "default",894 "permission_mode": "default",
700 "hook_event_name": "UserPromptSubmit",895 "hook_event_name": "PostToolUseFailure",
701 "prompt": "Write a function to calculate the factorial of a number"896 "tool_name": "Bash",
897 "tool_input": {
898 "command": "npm test",
899 "description": "Run test suite"
900 },
901 "tool_use_id": "toolu_01ABC123...",
902 "error": "Command exited with non-zero status code 1",
903 "is_interrupt": false
702}904}
703```905```
704 906
705### Stop Input907| Field | Description |
908| :------------- | :------------------------------------------------------------------------------ |
909| `error` | String describing what went wrong |
910| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |
706 911
707`stop_hook_active` is true when Claude Code is already continuing as a result of912#### PostToolUseFailure decision control
708a stop hook. Check this value or process the transcript to prevent Claude Code913
709from running indefinitely.914`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:
915
916| Field | Description |
917| :------------------ | :------------------------------------------------------------ |
918| `additionalContext` | Additional context for Claude to consider alongside the error |
710 919
711```json theme={null}920```json theme={null}
712{921{
713 "session_id": "abc123",922 "hookSpecificOutput": {
714 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",923 "hookEventName": "PostToolUseFailure",
715 "cwd": "/Users/...",924 "additionalContext": "Additional information about the failure for Claude"
716 "permission_mode": "default",925 }
717 "hook_event_name": "Stop",
718 "stop_hook_active": true
719}926}
720```927```
721 928
722### SubagentStop Input929### Notification
930
931Runs 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.
723 932
724Triggered when a subagent finishes. 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.933Use 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:
725 934
726```json theme={null}935```json theme={null}
727{936{
728 "session_id": "abc123",937 "hooks": {
729 "transcript_path": "~/.claude/projects/.../abc123.jsonl",938 "Notification": [
730 "cwd": "/Users/...",939 {
731 "permission_mode": "default",940 "matcher": "permission_prompt",
732 "hook_event_name": "SubagentStop",941 "hooks": [
733 "stop_hook_active": false,942 {
734 "agent_id": "def456",943 "type": "command",
735 "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl"944 "command": "/path/to/permission-alert.sh"
945 }
946 ]
947 },
948 {
949 "matcher": "idle_prompt",
950 "hooks": [
951 {
952 "type": "command",
953 "command": "/path/to/idle-notification.sh"
954 }
955 ]
956 }
957 ]
958 }
736}959}
737```960```
738 961
739### PreCompact Input962#### Notification input
740 963
741For `manual`, `custom_instructions` comes from what the user passes into964In 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.
742`/compact`. For `auto`, `custom_instructions` is empty.
743 965
744```json theme={null}966```json theme={null}
745{967{
746 "session_id": "abc123",968 "session_id": "abc123",
747 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",969 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
970 "cwd": "/Users/...",
748 "permission_mode": "default",971 "permission_mode": "default",
749 "hook_event_name": "PreCompact",972 "hook_event_name": "Notification",
750 "trigger": "manual",973 "message": "Claude needs your permission to use Bash",
751 "custom_instructions": ""974 "title": "Permission needed",
975 "notification_type": "permission_prompt"
752}976}
753```977```
754 978
755### Setup Input979Notification 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:
980
981| Field | Description |
982| :------------------ | :------------------------------- |
983| `additionalContext` | String added to Claude's context |
984
985### SubagentStart
986
987Runs 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/`).
988
989#### SubagentStart input
990
991In 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).
756 992
757```json theme={null}993```json theme={null}
758{994{
759 "session_id": "abc123",995 "session_id": "abc123",
760 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",996 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
761 "cwd": "/Users/...",997 "cwd": "/Users/...",
762 "permission_mode": "default",998 "permission_mode": "default",
763 "hook_event_name": "Setup",999 "hook_event_name": "SubagentStart",
764 "trigger": "init"1000 "agent_id": "agent-abc123",
1001 "agent_type": "Explore"
765}1002}
766```1003```
767 1004
768The `trigger` field will be either `"init"` (from `--init` or `--init-only`) or `"maintenance"` (from `--maintenance`).1005SubagentStart 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:
769 1006
770### SessionStart Input1007| Field | Description |
1008| :------------------ | :------------------------------------- |
1009| `additionalContext` | String added to the subagent's context |
771 1010
772```json theme={null}1011```json theme={null}
773{1012{
774 "session_id": "abc123",1013 "hookSpecificOutput": {
775 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1014 "hookEventName": "SubagentStart",
776 "cwd": "/Users/...",1015 "additionalContext": "Follow security guidelines for this task"
777 "permission_mode": "default",1016 }
778 "hook_event_name": "SessionStart",
779 "source": "startup",
780 "model": "claude-sonnet-4-20250514"
781}1017}
782```1018```
783 1019
784The `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 when available. If you start Claude Code with `claude --agent <name>`, an `agent_type` field contains the agent name.1020### SubagentStop
1021
1022Runs when a Claude Code subagent has finished responding. Matches on agent type, same values as SubagentStart.
1023
1024#### SubagentStop input
785 1025
786### SubagentStart Input1026In addition to the [common input fields](#common-input-fields), SubagentStop hooks receive `stop_hook_active`, `agent_id`, `agent_type`, and `agent_transcript_path`. The `agent_type` field is the value used for matcher filtering. The `transcript_path` is the main session's transcript, while `agent_transcript_path` is the subagent's own transcript stored in a nested `subagents/` folder.
787 1027
788```json theme={null}1028```json theme={null}
789{1029{
790 "session_id": "abc123",1030 "session_id": "abc123",
791 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1031 "transcript_path": "~/.claude/projects/.../abc123.jsonl",
792 "cwd": "/Users/...",1032 "cwd": "/Users/...",
793 "permission_mode": "default",1033 "permission_mode": "default",
794 "hook_event_name": "SubagentStart",1034 "hook_event_name": "SubagentStop",
795 "agent_id": "agent-abc123",1035 "stop_hook_active": false,
796 "agent_type": "Explore"1036 "agent_id": "def456",
1037 "agent_type": "Explore",
1038 "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl"
797}1039}
798```1040```
799 1041
800Triggered when a subagent is spawned. The `agent_id` field contains the unique identifier for the subagent, and `agent_type` contains the agent name (built-in agents like `"Bash"`, `"Explore"`, `"Plan"`, or custom agent names).1042SubagentStop hooks use the same decision control format as [Stop hooks](#stop-decision-control).
1043
1044### Stop
1045
1046Runs when the main Claude Code agent has finished responding. Does not run if
1047the stoppage occurred due to a user interrupt.
801 1048
802### SessionEnd Input1049#### Stop input
1050
1051In addition to the [common input fields](#common-input-fields), Stop hooks receive `stop_hook_active`. This field is `true` when Claude Code is already continuing as a result of a stop hook. Check this value or process the transcript to prevent Claude Code from running indefinitely.
803 1052
804```json theme={null}1053```json theme={null}
805{1054{
807 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1056 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
808 "cwd": "/Users/...",1057 "cwd": "/Users/...",
809 "permission_mode": "default",1058 "permission_mode": "default",
810 "hook_event_name": "SessionEnd",1059 "hook_event_name": "Stop",
811 "reason": "exit"1060 "stop_hook_active": true
812}1061}
813```1062```
814 1063
815## Hook Output1064#### Stop decision control
816
817There are two mutually exclusive ways for hooks to return output back to Claude Code. The output
818communicates whether to block and any feedback that should be shown to Claude
819and the user.
820 1065
821### Simple: Exit Code1066`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:
822 1067
823Hooks communicate status through exit codes, stdout, and stderr:1068| Field | Description |
824 1069| :--------- | :------------------------------------------------------------------------- |
825* **Exit code 0**: Success. `stdout` is shown to the user in verbose mode1070| `decision` | `"block"` prevents Claude from stopping. Omit to allow Claude to stop |
826 (ctrl+o), except for `UserPromptSubmit` and `SessionStart`, where stdout is1071| `reason` | Required when `decision` is `"block"`. Tells Claude why it should continue |
827 added to the context. JSON output in `stdout` is parsed for structured control
828 (see [Advanced: JSON Output](#advanced-json-output)).
829* **Exit code 2**: Blocking error. Only `stderr` is used as the error message
830 and fed back to Claude. The format is `[command]: {stderr}`. JSON in `stdout`
831 is **not** processed for exit code 2. See per-hook-event behavior below.
832* **Other exit codes**: Non-blocking error. `stderr` is shown to the user in verbose mode (ctrl+o) with
833 format `Failed with non-blocking status code: {stderr}`. If `stderr` is empty,
834 it shows `No stderr output`. Execution continues.
835
836<Warning>
837 Reminder: Claude Code does not see stdout if the exit code is 0, except for
838 the `UserPromptSubmit` hook where stdout is injected as context.
839</Warning>
840
841#### Exit Code 2 Behavior
842
843| Hook Event | Behavior |
844| ------------------- | ------------------------------------------------------------------ |
845| `PreToolUse` | Blocks the tool call, shows stderr to Claude |
846| `PermissionRequest` | Denies the permission, shows stderr to Claude |
847| `PostToolUse` | Shows stderr to Claude (tool already ran) |
848| `Notification` | N/A, shows stderr to user only |
849| `UserPromptSubmit` | Blocks prompt processing, erases prompt, shows stderr to user only |
850| `Stop` | Blocks stoppage, shows stderr to Claude |
851| `SubagentStop` | Blocks stoppage, shows stderr to Claude subagent |
852| `PreCompact` | N/A, shows stderr to user only |
853| `Setup` | N/A, shows stderr to user only |
854| `SessionStart` | N/A, shows stderr to user only |
855| `SessionEnd` | N/A, shows stderr to user only |
856
857### Advanced: JSON Output
858
859Hooks can return structured JSON in `stdout` for more sophisticated control.
860
861<Warning>
862 JSON output is only processed when the hook exits with code 0. If your hook
863 exits with code 2 (blocking error), `stderr` text is used directly—any JSON in `stdout`
864 is ignored. For other non-zero exit codes, only `stderr` is shown to the user in verbose mode (ctrl+o).
865</Warning>
866
867#### Common JSON Fields
868
869All hook types can include these optional fields:
870 1072
871```json theme={null}1073```json theme={null}
872{1074{
873 "continue": true, // Whether Claude should continue after hook execution (default: true)1075 "decision": "block",
874 "stopReason": "string", // Message shown when continue is false1076 "reason": "Must be provided when Claude is blocked from stopping"
875
876 "suppressOutput": true, // Hide stdout from transcript mode (default: false)
877 "systemMessage": "string" // Optional warning message shown to the user
878}1077}
879```1078```
880 1079
881If `continue` is false, Claude stops processing after the hooks run.1080### PreCompact
882
883* For `PreToolUse`, this is different from `"permissionDecision": "deny"`, which
884 only blocks a specific tool call and provides automatic feedback to Claude.
885* For `PostToolUse`, this is different from `"decision": "block"`, which
886 provides automated feedback to Claude.
887* For `UserPromptSubmit`, this prevents the prompt from being processed.
888* For `Stop` and `SubagentStop`, this takes precedence over any
889 `"decision": "block"` output.
890* In all cases, `"continue" = false` takes precedence over any
891 `"decision": "block"` output.
892
893`stopReason` accompanies `continue` with a reason shown to the user, not shown
894to Claude.
895
896#### `PreToolUse` Decision Control
897
898`PreToolUse` hooks can control whether a tool call proceeds.
899 1081
900* `"allow"` bypasses the permission system. `permissionDecisionReason` is shown1082Runs before Claude Code is about to run a compact operation.
901 to the user but not to Claude.
902* `"deny"` prevents the tool call from executing. `permissionDecisionReason` is
903 shown to Claude.
904* `"ask"` asks the user to confirm the tool call in the UI.
905 `permissionDecisionReason` is shown to the user but not to Claude.
906 1083
907Additionally, hooks can modify tool inputs before execution using `updatedInput`:1084The matcher value indicates whether compaction was triggered manually or automatically:
908 1085
909* `updatedInput` modifies the tool's input parameters before the tool executes1086| Matcher | When it fires |
910* Combine with `"permissionDecision": "allow"` to modify the input and auto-approve the tool call1087| :------- | :------------------------------------------- |
911* Combine with `"permissionDecision": "ask"` to modify the input and show it to the user for confirmation1088| `manual` | `/compact` |
1089| `auto` | Auto-compact when the context window is full |
912 1090
913Hooks can also provide context to Claude using `additionalContext`:1091#### PreCompact input
914 1092
915* `"hookSpecificOutput.additionalContext"` adds a string to Claude's context before the tool executes.1093In 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.
916 1094
917```json theme={null}1095```json theme={null}
918{1096{
919 "hookSpecificOutput": {1097 "session_id": "abc123",
920 "hookEventName": "PreToolUse",1098 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
921 "permissionDecision": "allow",1099 "cwd": "/Users/...",
922 "permissionDecisionReason": "My reason here",1100 "permission_mode": "default",
923 "updatedInput": {1101 "hook_event_name": "PreCompact",
924 "field_to_modify": "new value"1102 "trigger": "manual",
925 },1103 "custom_instructions": ""
926 "additionalContext": "Current environment: production. Proceed with caution."
927 }
928}1104}
929```1105```
930 1106
931<Note>1107### SessionEnd
932 The `decision` and `reason` fields are deprecated for PreToolUse hooks.
933 Use `hookSpecificOutput.permissionDecision` and
934 `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields
935 `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.
936</Note>
937
938#### `PermissionRequest` Decision Control
939
940`PermissionRequest` hooks can allow or deny permission requests shown to the user.
941 1108
942* For `"behavior": "allow"` you can also optionally pass in an `"updatedInput"` that modifies the tool's input parameters before the tool executes.1109Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
943* 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.1110statistics, or saving session state. Supports matchers to filter by exit reason.
944 1111
945```json theme={null}1112The `reason` field in the hook input indicates why the session ended:
946{
947 "hookSpecificOutput": {
948 "hookEventName": "PermissionRequest",
949 "decision": {
950 "behavior": "allow",
951 "updatedInput": {
952 "command": "npm run lint"
953 }
954 }
955 }
956}
957```
958 1113
959#### `PostToolUse` Decision Control1114| Reason | Description |
1115| :---------------------------- | :----------------------------------------- |
1116| `clear` | Session cleared with `/clear` command |
1117| `logout` | User logged out |
1118| `prompt_input_exit` | User exited while prompt input was visible |
1119| `bypass_permissions_disabled` | Bypass permissions mode was disabled |
1120| `other` | Other exit reasons |
960 1121
961`PostToolUse` hooks can provide feedback to Claude after tool execution.1122#### SessionEnd input
962 1123
963* `"block"` automatically prompts Claude with `reason`.1124In 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.
964* `undefined` does nothing. `reason` is ignored.
965* `"hookSpecificOutput.additionalContext"` adds context for Claude to consider.
966 1125
967```json theme={null}1126```json theme={null}
968{1127{
969 "decision": "block" | undefined,1128 "session_id": "abc123",
970 "reason": "Explanation for decision",1129 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
971 "hookSpecificOutput": {1130 "cwd": "/Users/...",
972 "hookEventName": "PostToolUse",1131 "permission_mode": "default",
973 "additionalContext": "Additional information for Claude"1132 "hook_event_name": "SessionEnd",
974 }1133 "reason": "other"
975}1134}
976```1135```
977 1136
978#### `UserPromptSubmit` Decision Control1137SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.
979 1138
980`UserPromptSubmit` hooks can control whether a user prompt is processed and add context.1139## Prompt-based hooks
981 1140
982**Adding context (exit code 0):**1141In addition to Bash command hooks (`type: "command"`), Claude Code supports prompt-based hooks (`type: "prompt"`) that use an LLM to evaluate whether to allow or block an action. Prompt-based hooks work with the following events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `UserPromptSubmit`, `Stop`, and `SubagentStop`.
983There are two ways to add context to the conversation:
984 1142
9851. **Plain text stdout** (simpler): Any non-JSON text written to stdout is added1143### How prompt-based hooks work
986 as context. This is the easiest way to inject information.
987 1144
9882. **JSON with `additionalContext`** (structured): Use the JSON format below for1145Instead of executing a Bash command, prompt-based hooks:
989 more control. The `additionalContext` field is added as context.1146
11471. Send the hook input and your prompt to a Claude model, Haiku by default
11482. The LLM responds with structured JSON containing a decision
11493. Claude Code processes the decision automatically
990 1150
991Both methods work with exit code 0. Plain stdout is shown as hook output in1151### Prompt hook configuration
992the transcript; `additionalContext` is added more discretely.
993 1152
994**Blocking prompts:**1153Set `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.
995 1154
996* `"decision": "block"` prevents the prompt from being processed. The submitted1155This `Stop` hook asks the LLM to evaluate whether all tasks are complete before allowing Claude to finish:
997 prompt is erased from context. `"reason"` is shown to the user but not added
998 to context.
999* `"decision": undefined` (or omitted) allows the prompt to proceed normally.
1000 1156
1001```json theme={null}1157```json theme={null}
1002{1158{
1003 "decision": "block" | undefined,1159 "hooks": {
1004 "reason": "Explanation for decision",1160 "Stop": [
1005 "hookSpecificOutput": {1161 {
1006 "hookEventName": "UserPromptSubmit",1162 "hooks": [
1007 "additionalContext": "My additional context here"1163 {
1164 "type": "prompt",
1165 "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
1166 }
1167 ]
1168 }
1169 ]
1008 }1170 }
1009}1171}
1010```1172```
1011 1173
1012<Note>1174| Field | Required | Description |
1013 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 to1175| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1014 block prompts or want more structured control.1176| `type` | yes | Must be `"prompt"` |
1015</Note>1177| `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 |
1016 1178| `model` | no | Model to use for evaluation. Defaults to a fast model |
1017#### `Stop`/`SubagentStop` Decision Control1179| `timeout` | no | Timeout in seconds. Default: 30 |
1018 1180
1019`Stop` and `SubagentStop` hooks can control whether Claude must continue.1181### Response schema
1020 1182
1021* `"block"` prevents Claude from stopping. You must populate `reason` for Claude1183The LLM must respond with JSON containing:
1022 to know how to proceed.
1023* `undefined` allows Claude to stop. `reason` is ignored.
1024 1184
1025```json theme={null}1185```json theme={null}
1026{1186{
1027 "decision": "block" | undefined,1187 "ok": true | false,
1028 "reason": "Must be provided when Claude is blocked from stopping"1188 "reason": "Explanation for the decision"
1029}1189}
1030```1190```
1031 1191
1032#### `Setup` Decision Control1192| Field | Description |
1193| :------- | :--------------------------------------------------------- |
1194| `ok` | `true` allows the action, `false` prevents it |
1195| `reason` | Required when `ok` is `false`. Explanation shown to Claude |
1033 1196
1034`Setup` hooks allow you to load context and configure the environment during repository initialization or maintenance.1197### Example: Multi-criteria Stop hook
1035 1198
1036* `"hookSpecificOutput.additionalContext"` adds the string to the context.1199This `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:
1037* Multiple hooks' `additionalContext` values are concatenated.
1038* Setup hooks have access to `CLAUDE_ENV_FILE` for persisting environment variables.
1039 1200
1040```json theme={null}1201```json theme={null}
1041{1202{
1042 "hookSpecificOutput": {1203 "hooks": {
1043 "hookEventName": "Setup",1204 "Stop": [
1044 "additionalContext": "Repository initialized with custom configuration"1205 {
1206 "hooks": [
1207 {
1208 "type": "prompt",
1209 "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.",
1210 "timeout": 30
1211 }
1212 ]
1213 }
1214 ]
1045 }1215 }
1046}1216}
1047```1217```
1048 1218
1049#### `SessionStart` Decision Control1219## Agent-based hooks
1050 1220
1051`SessionStart` hooks allow you to load in context at the start of a session.1221Agent-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.
1052 1222
1053* `"hookSpecificOutput.additionalContext"` adds the string to the context.1223### How agent hooks work
1054* Multiple hooks' `additionalContext` values are concatenated.
1055 1224
1056```json theme={null}1225When an agent hook fires:
1057{
1058 "hookSpecificOutput": {
1059 "hookEventName": "SessionStart",
1060 "additionalContext": "My additional context here"
1061 }
1062}
1063```
1064 1226
1065#### `SessionEnd` Decision Control12271. Claude Code spawns a subagent with your prompt and the hook's JSON input
1066 12282. The subagent can use tools like Read, Grep, and Glob to investigate
1067`SessionEnd` hooks run when a session ends. They cannot block session termination12293. After up to 50 turns, the subagent returns a structured `{ "ok": true/false }` decision
1068but can perform cleanup tasks.12304. Claude Code processes the decision the same way as a prompt hook
1069
1070#### Exit Code Example: Bash Command Validation
1071
1072```python theme={null}
1073#!/usr/bin/env python3
1074import json
1075import re
1076import sys
1077
1078# Define validation rules as a list of (regex pattern, message) tuples
1079VALIDATION_RULES = [
1080 (
1081 r"\bgrep\b(?!.*\|)",
1082 "Use 'rg' (ripgrep) instead of 'grep' for better performance and features",
1083 ),
1084 (
1085 r"\bfind\s+\S+\s+-name\b",
1086 "Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",
1087 ),
1088]
1089
1090
1091def validate_command(command: str) -> list[str]:
1092 issues = []
1093 for pattern, message in VALIDATION_RULES:
1094 if re.search(pattern, command):
1095 issues.append(message)
1096 return issues
1097
1098
1099try:
1100 input_data = json.load(sys.stdin)
1101except json.JSONDecodeError as e:
1102 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
1103 sys.exit(1)
1104
1105tool_name = input_data.get("tool_name", "")
1106tool_input = input_data.get("tool_input", {})
1107command = tool_input.get("command", "")
1108
1109if tool_name != "Bash" or not command:
1110 sys.exit(1)
1111
1112# Validate the command
1113issues = validate_command(command)
1114
1115if issues:
1116 for message in issues:
1117 print(f"• {message}", file=sys.stderr)
1118 # Exit code 2 blocks tool call and shows stderr to Claude
1119 sys.exit(2)
1120```
1121 1231
1122#### JSON Output Example: UserPromptSubmit to Add Context and Validation1232Agent hooks are useful when verification requires inspecting actual files or test output, not just evaluating the hook input data alone.
1123 1233
1124<Note>1234### Agent hook configuration
1125 For `UserPromptSubmit` hooks, you can inject context using either method:
1126 1235
1127 * **Plain text stdout** with exit code 0: Simplest approach, prints text1236Set `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:
1128 * **JSON output** with exit code 0: Use `"decision": "block"` to reject prompts,
1129 or `additionalContext` for structured context injection
1130 1237
1131 Remember: Exit code 2 only uses `stderr` for the error message. To block using1238| Field | Required | Description |
1132 JSON (with a custom reason), use `"decision": "block"` with exit code 0.1239| :-------- | :------- | :------------------------------------------------------------------------------------------ |
1133</Note>1240| `type` | yes | Must be `"agent"` |
1134 1241| `prompt` | yes | Prompt describing what to verify. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
1135```python theme={null}1242| `model` | no | Model to use. Defaults to a fast model |
1136#!/usr/bin/env python31243| `timeout` | no | Timeout in seconds. Default: 60 |
1137import json
1138import sys
1139import re
1140import datetime
1141
1142# Load input from stdin
1143try:
1144 input_data = json.load(sys.stdin)
1145except json.JSONDecodeError as e:
1146 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
1147 sys.exit(1)
1148
1149prompt = input_data.get("prompt", "")
1150
1151# Check for sensitive patterns
1152sensitive_patterns = [
1153 (r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),
1154]
1155
1156for pattern, message in sensitive_patterns:
1157 if re.search(pattern, prompt):
1158 # Use JSON output to block with a specific reason
1159 output = {
1160 "decision": "block",
1161 "reason": f"Security policy violation: {message}. Please rephrase your request without sensitive information."
1162 }
1163 print(json.dumps(output))
1164 sys.exit(0)
1165 1244
1166# Add current time to context1245The response schema is the same as prompt hooks: `{ "ok": true }` to allow or `{ "ok": false, "reason": "..." }` to block.
1167context = f"Current time: {datetime.datetime.now()}"
1168print(context)
1169 1246
1170"""1247This `Stop` hook verifies that all unit tests pass before allowing Claude to finish:
1171The following is also equivalent:
1172print(json.dumps({
1173 "hookSpecificOutput": {
1174 "hookEventName": "UserPromptSubmit",
1175 "additionalContext": context,
1176 },
1177}))
1178"""
1179 1248
1180# Allow the prompt to proceed with the additional context1249```json theme={null}
1181sys.exit(0)1250{
1182```1251 "hooks": {
1183 1252 "Stop": [
1184#### JSON Output Example: PreToolUse with Approval1253 {
1185 1254 "hooks": [
1186```python theme={null}1255 {
1187#!/usr/bin/env python31256 "type": "agent",
1188import json1257 "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
1189import sys1258 "timeout": 120
1190
1191# Load input from stdin
1192try:
1193 input_data = json.load(sys.stdin)
1194except json.JSONDecodeError as e:
1195 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
1196 sys.exit(1)
1197
1198tool_name = input_data.get("tool_name", "")
1199tool_input = input_data.get("tool_input", {})
1200
1201# Example: Auto-approve file reads for documentation files
1202if tool_name == "Read":
1203 file_path = tool_input.get("file_path", "")
1204 if file_path.endswith((".md", ".mdx", ".txt", ".json")):
1205 # Use JSON output to auto-approve the tool call
1206 output = {
1207 "decision": "approve",
1208 "reason": "Documentation file auto-approved",
1209 "suppressOutput": True # Don't show in verbose mode
1210 }1259 }
1211 print(json.dumps(output))1260 ]
1212 sys.exit(0)1261 }
1213 1262 ]
1214# For other cases, let the normal permission flow proceed1263 }
1215sys.exit(0)1264}
1216```1265```
1217 1266
1218## Working with MCP Tools1267## Run hooks in the background
1219
1220Claude Code hooks work seamlessly with
1221[Model Context Protocol (MCP) tools](/en/mcp). When MCP servers
1222provide tools, they appear with a special naming pattern that you can match in
1223your hooks.
1224
1225### MCP Tool Naming
1226 1268
1227MCP tools follow the pattern `mcp__<server>__<tool>`, for example:1269By 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.
1228 1270
1229* `mcp__memory__create_entities` - Memory server's create entities tool1271### Configure an async hook
1230* `mcp__filesystem__read_file` - Filesystem server's read file tool
1231* `mcp__github__search_repositories` - GitHub server's search tool
1232 1272
1233### Configuring Hooks for MCP Tools1273Add `"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.
1234 1274
1235You can target specific MCP tools or entire MCP servers:1275This 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:
1236 1276
1237```json theme={null}1277```json theme={null}
1238{1278{
1239 "hooks": {1279 "hooks": {
1240 "PreToolUse": [1280 "PostToolUse": [
1241 {
1242 "matcher": "mcp__memory__.*",
1243 "hooks": [
1244 {
1245 "type": "command",
1246 "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
1247 }
1248 ]
1249 },
1250 {1281 {
1251 "matcher": "mcp__.*__write.*",1282 "matcher": "Write",
1252 "hooks": [1283 "hooks": [
1253 {1284 {
1254 "type": "command",1285 "type": "command",
1255 "command": "/home/user/scripts/validate-mcp-write.py"1286 "command": "/path/to/run-tests.sh",
1287 "async": true,
1288 "timeout": 120
1256 }1289 }
1257 ]1290 ]
1258 }1291 }
1261}1294}
1262```1295```
1263 1296
1264## Examples1297The `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.
1265
1266<Tip>
1267 For practical examples including code formatting, notifications, and file protection, see [More Examples](/en/hooks-guide#more-examples) in the get started guide.
1268</Tip>
1269
1270## Security Considerations
1271 1298
1272### Disclaimer1299### How async hooks execute
1273 1300
1274**USE AT YOUR OWN RISK**: Claude Code hooks execute arbitrary shell commands on1301When 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.
1275your system automatically. By using hooks, you acknowledge that:
1276 1302
1277* You are solely responsible for the commands you configure1303After 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.
1278* Hooks can modify, delete, or access any files your user account can access
1279* Malicious or poorly written hooks can cause data loss or system damage
1280* Anthropic provides no warranty and assumes no liability for any damages
1281 resulting from hook usage
1282* You should thoroughly test hooks in a safe environment before production use
1283 1304
1284Always review and understand any hook commands before adding them to your1305### Example: run tests after file changes
1285configuration.
1286 1306
1287### Security Best Practices1307This 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`:
1288 1308
1289Here are some key practices for writing more secure hooks:1309```bash theme={null}
1310#!/bin/bash
1311# run-tests-async.sh
1290 1312
12911. **Validate and sanitize inputs** - Never trust input data blindly1313# Read hook input from stdin
12922. **Always quote shell variables** - Use `"$VAR"` not `$VAR`1314INPUT=$(cat)
12933. **Block path traversal** - Check for `..` in file paths1315FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
12944. **Use absolute paths** - Specify full paths for scripts (use
1295 "\$CLAUDE\_PROJECT\_DIR" for the project path)
12965. **Skip sensitive files** - Avoid `.env`, `.git/`, keys, etc.
1297 1316
1298### Configuration Safety1317# Only run tests for source files
1318if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
1319 exit 0
1320fi
1299 1321
1300Direct edits to hooks in settings files don't take effect immediately. Claude1322# Run tests and report results via systemMessage
1301Code:1323RESULT=$(npm test 2>&1)
1324EXIT_CODE=$?
1302 1325
13031. Captures a snapshot of hooks at startup1326if [ $EXIT_CODE -eq 0 ]; then
13042. Uses this snapshot throughout the session1327 echo "{\"systemMessage\": \"Tests passed after editing $FILE_PATH\"}"
13053. Warns if hooks are modified externally1328else
13064. Requires review in `/hooks` menu for changes to apply1329 echo "{\"systemMessage\": \"Tests failed after editing $FILE_PATH: $RESULT\"}"
1330fi
1331```
1307 1332
1308This prevents malicious hook modifications from affecting your current session.1333Then add this configuration to `.claude/settings.json` in your project root. The `async: true` flag lets Claude keep working while tests run:
1309 1334
1310## Hook Execution Details1335```json theme={null}
1336{
1337 "hooks": {
1338 "PostToolUse": [
1339 {
1340 "matcher": "Write|Edit",
1341 "hooks": [
1342 {
1343 "type": "command",
1344 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests-async.sh",
1345 "async": true,
1346 "timeout": 300
1347 }
1348 ]
1349 }
1350 ]
1351 }
1352}
1353```
1311 1354
1312* **Timeout**: 60-second execution limit by default, configurable per command.1355### Limitations
1313 * A timeout for an individual command does not affect the other commands.
1314* **Parallelization**: All matching hooks run in parallel
1315* **Deduplication**: Multiple identical hook commands are deduplicated automatically
1316* **Environment**: Runs in current directory with Claude Code's environment
1317 * The `CLAUDE_PROJECT_DIR` environment variable is available and contains the
1318 absolute path to the project root directory (where Claude Code was started)
1319 * 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.
1320* **Input**: JSON via stdin
1321* **Output**:
1322 * PreToolUse/PermissionRequest/PostToolUse/Stop/SubagentStop: Progress shown in verbose mode (ctrl+o)
1323 * Notification/SessionEnd: Logged to debug only (`--debug`)
1324 * UserPromptSubmit/SessionStart/Setup: stdout added as context for Claude
1325 1356
1326## Debugging1357Async hooks have several constraints compared to synchronous hooks:
1327 1358
1328### Basic Troubleshooting1359* Only `type: "command"` hooks support `async`. Prompt-based hooks cannot run asynchronously.
1360* Async hooks cannot block tool calls or return decisions. By the time the hook completes, the triggering action has already proceeded.
1361* Hook output is delivered on the next conversation turn. If the session is idle, the response waits until the next user interaction.
1362* Each execution creates a separate background process. There is no deduplication across multiple firings of the same async hook.
1329 1363
1330If your hooks aren't working:1364## Security considerations
1331 1365
13321. **Check configuration** - Run `/hooks` to see if your hook is registered1366### Disclaimer
13332. **Verify syntax** - Ensure your JSON settings are valid
13343. **Test commands** - Run hook commands manually first
13354. **Check permissions** - Make sure scripts are executable
13365. **Review logs** - Use `claude --debug` to see hook execution details
1337 1367
1338Common issues:1368Hooks run with your system user's full permissions.
1339 1369
1340* **Quotes not escaped** - Use `\"` inside JSON strings1370<Warning>
1341* **Wrong matcher** - Check tool names match exactly (case-sensitive)1371 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.
1342* **Command not found** - Use full paths for scripts1372</Warning>
1343 1373
1344### Advanced Debugging1374### Security best practices
1345 1375
1346For complex hook issues:1376Keep these practices in mind when writing hooks:
1347 1377
13481. **Inspect hook execution** - Use `claude --debug` to see detailed hook1378* **Validate and sanitize inputs**: never trust input data blindly
1349 execution1379* **Always quote shell variables**: use `"$VAR"` not `$VAR`
13502. **Validate JSON schemas** - Test hook input/output with external tools1380* **Block path traversal**: check for `..` in file paths
13513. **Check environment variables** - Verify Claude Code's environment is correct1381* **Use absolute paths**: specify full paths for scripts, using `"$CLAUDE_PROJECT_DIR"` for the project root
13524. **Test edge cases** - Try hooks with unusual file paths or inputs1382* **Skip sensitive files**: avoid `.env`, `.git/`, keys, etc.
13535. **Monitor system resources** - Check for resource exhaustion during hook
1354 execution
13556. **Use structured logging** - Implement logging in your hook scripts
1356 1383
1357### Debug Output Example1384## Debug hooks
1358 1385
1359Use `claude --debug` to see hook execution details:1386Run `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.
1360 1387
1361```1388```
1362[DEBUG] Executing hooks for PostToolUse:Write1389[DEBUG] Executing hooks for PostToolUse:Write
1364[DEBUG] Found 1 hook matchers in settings1391[DEBUG] Found 1 hook matchers in settings
1365[DEBUG] Matched 1 hooks for query "Write"1392[DEBUG] Matched 1 hooks for query "Write"
1366[DEBUG] Found 1 hook commands to execute1393[DEBUG] Found 1 hook commands to execute
1367[DEBUG] Executing hook command: <Your command> with timeout 60000ms1394[DEBUG] Executing hook command: <Your command> with timeout 600000ms
1368[DEBUG] Hook command completed with status 0: <Your stdout>1395[DEBUG] Hook command completed with status 0: <Your stdout>
1369```1396```
1370 1397
1371Progress messages appear in verbose mode (ctrl+o) showing:1398For 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.
1372
1373* Which hook is running
1374* Command being executed
1375* Success/failure status
1376* Output or error messages