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