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