SpyBara
Go Premium

Documentation 2026-01-30 18:07 UTC to 2026-01-31 03:42 UTC

13 files changed +1,443 −1,120. View all changes and history on the product overview
2026
Sat 31 03:42 Fri 30 18:07 Thu 29 21:03 Wed 28 15:06 Tue 27 21:01 Mon 26 21:03 Sun 25 03:34 Sat 24 03:29 Fri 23 21:01 Thu 22 21:03 Wed 21 21:05 Tue 20 21:03 Mon 19 21:01 Fri 16 21:01 Wed 14 06:02 Mon 12 21:02 Sun 11 18:02 Sat 10 21:01 Fri 9 21:01 Thu 8 21:02 Wed 7 21:01 Tue 6 21:01 Sat 3 18:02
Details

42| `--disallowedTools` | Tools that are removed from the model's context and cannot be used | `"Bash(git log *)" "Bash(git diff *)" "Edit"` |42| `--disallowedTools` | Tools that are removed from the model's context and cannot be used | `"Bash(git log *)" "Bash(git diff *)" "Edit"` |

43| `--fallback-model` | Enable automatic fallback to specified model when default model is overloaded (print mode only) | `claude -p --fallback-model sonnet "query"` |43| `--fallback-model` | Enable automatic fallback to specified model when default model is overloaded (print mode only) | `claude -p --fallback-model sonnet "query"` |

44| `--fork-session` | When resuming, create a new session ID instead of reusing the original (use with `--resume` or `--continue`) | `claude --resume abc123 --fork-session` |44| `--fork-session` | When resuming, create a new session ID instead of reusing the original (use with `--resume` or `--continue`) | `claude --resume abc123 --fork-session` |

45| `--from-pr` | Resume sessions linked to a specific GitHub PR. Accepts a PR number or URL. Sessions are automatically linked when created via `gh pr create` | `claude --from-pr 123` |

45| `--ide` | Automatically connect to IDE on startup if exactly one valid IDE is available | `claude --ide` |46| `--ide` | Automatically connect to IDE on startup if exactly one valid IDE is available | `claude --ide` |

46| `--init` | Run [Setup hooks](/en/hooks#setup) and start interactive mode | `claude --init` |47| `--init` | Run initialization hooks and start interactive mode | `claude --init` |

47| `--init-only` | Run [Setup hooks](/en/hooks#setup) and exit (no interactive session) | `claude --init-only` |48| `--init-only` | Run initialization hooks and exit (no interactive session) | `claude --init-only` |

48| `--include-partial-messages` | Include partial streaming events in output (requires `--print` and `--output-format=stream-json`) | `claude -p --output-format stream-json --include-partial-messages "query"` |49| `--include-partial-messages` | Include partial streaming events in output (requires `--print` and `--output-format=stream-json`) | `claude -p --output-format stream-json --include-partial-messages "query"` |

49| `--input-format` | Specify input format for print mode (options: `text`, `stream-json`) | `claude -p --output-format json --input-format stream-json` |50| `--input-format` | Specify input format for print mode (options: `text`, `stream-json`) | `claude -p --output-format json --input-format stream-json` |

50| `--json-schema` | Get validated JSON output matching a JSON Schema after agent completes its workflow (print mode only, see [Agent SDK Structured Outputs](https://docs.claude.com/en/docs/agent-sdk/structured-outputs)) | `claude -p --json-schema '{"type":"object","properties":{...}}' "query"` |51| `--json-schema` | Get validated JSON output matching a JSON Schema after agent completes its workflow (print mode only, see [Agent SDK Structured Outputs](https://docs.claude.com/en/docs/agent-sdk/structured-outputs)) | `claude -p --json-schema '{"type":"object","properties":{...}}' "query"` |

51| `--maintenance` | Run [Setup hooks](/en/hooks#setup) with maintenance trigger and exit | `claude --maintenance` |52| `--maintenance` | Run maintenance hooks and exit | `claude --maintenance` |

52| `--max-budget-usd` | Maximum dollar amount to spend on API calls before stopping (print mode only) | `claude -p --max-budget-usd 5.00 "query"` |53| `--max-budget-usd` | Maximum dollar amount to spend on API calls before stopping (print mode only) | `claude -p --max-budget-usd 5.00 "query"` |

53| `--max-turns` | Limit the number of agentic turns (print mode only). Exits with an error when the limit is reached. No limit by default | `claude -p --max-turns 3 "query"` |54| `--max-turns` | Limit the number of agentic turns (print mode only). Exits with an error when the limit is reached. No limit by default | `claude -p --max-turns 3 "query"` |

54| `--mcp-config` | Load MCP servers from JSON files or strings (space-separated) | `claude --mcp-config ./mcp.json` |55| `--mcp-config` | Load MCP servers from JSON files or strings (space-separated) | `claude --mcp-config ./mcp.json` |

Details

360 </Step>360 </Step>

361</Steps>361</Steps>

362 362 

363When you create a PR using `gh pr create`, the session is automatically linked to that PR. You can resume it later with `claude --from-pr <number>`.

364 

363<Tip>365<Tip>

364 Review Claude's generated PR before submitting and ask Claude to highlight potential risks or considerations.366 Review Claude's generated PR before submitting and ask Claude to highlight potential risks or considerations.

365</Tip>367</Tip>


560 562 

561* `claude --continue` continues the most recent conversation in the current directory563* `claude --continue` continues the most recent conversation in the current directory

562* `claude --resume` opens a conversation picker or resumes by name564* `claude --resume` opens a conversation picker or resumes by name

565* `claude --from-pr 123` resumes sessions linked to a specific pull request

563 566 

564From inside an active session, use `/resume` to switch to a different conversation.567From inside an active session, use `/resume` to switch to a different conversation.

565 568 

Details

202 </Tab>202 </Tab>

203 203 

204 <Tab title="Hooks">204 <Tab title="Hooks">

205 **When:** On trigger. Hooks can run before or after tool executions, at session start, before compaction, and at other lifecycle events. See [Hooks](/en/hooks) for the full list.205 **When:** On trigger. Hooks fire at specific lifecycle events like tool execution, session boundaries, prompt submission, permission requests, and compaction. See [Hooks](/en/hooks) for the full list.

206 206 

207 **What loads:** Nothing by default. Hooks run as external scripts.207 **What loads:** Nothing by default. Hooks run as external scripts.

208 208 


233 Connect Claude to external services233 Connect Claude to external services

234 </Card>234 </Card>

235 235 

236 <Card title="Hooks" icon="bolt" href="/en/hooks">236 <Card title="Hooks" icon="bolt" href="/en/hooks-guide">

237 Run scripts on Claude Code events237 Automate workflows with hooks

238 </Card>238 </Card>

239 239 

240 <Card title="Plugins" icon="puzzle-piece" href="/en/plugins">240 <Card title="Plugins" icon="puzzle-piece" href="/en/plugins">

hooks.md +909 −885

Details

4 4 

5# Hooks reference5# Hooks reference

6 6 

7> This page provides reference documentation for implementing hooks in Claude Code.7> Reference for Claude Code hook events, configuration schema, JSON input/output formats, exit codes, async hooks, prompt hooks, and MCP tool hooks.

8 8 

9<Tip>9<Tip>

10 For a quickstart guide with examples, see [Get started with Claude Code hooks](/en/hooks-guide).10 For a quickstart guide with examples, see [Automate workflows with hooks](/en/hooks-guide).

11</Tip>11</Tip>

12 12 

13Hooks are user-defined shell commands or LLM prompts that execute automatically at specific points in Claude Code's lifecycle. Use this reference to look up event schemas, configuration options, JSON input/output formats, and advanced features like async hooks and MCP tool hooks. If you're setting up hooks for the first time, start with the [guide](/en/hooks-guide) instead.

14 

13## Hook lifecycle15## Hook lifecycle

14 16 

15Hooks fire at specific points during a Claude Code session.17Hooks fire at specific points during a Claude Code session. When an event fires and a matcher matches, Claude Code passes JSON context about the event to your hook handler. For command hooks, this arrives on stdin. Your handler can then inspect the input, take action, and optionally return a decision. Some events fire once per session, while others fire repeatedly inside the agentic loop:

16 18 

17<div style={{maxWidth: "500px", margin: "0 auto"}}>19<div style={{maxWidth: "500px", margin: "0 auto"}}>

18 <Frame>20 <Frame>


20 </Frame>22 </Frame>

21</div>23</div>

22 24 

23| Hook | When it fires |25The table below summarizes when each event fires. The [Hook events](#hook-events) section documents the full input schema and decision control options for each one.

24| :------------------- | :------------------------------ |26 

25| `SessionStart` | Session begins or resumes |27| Event | When it fires |

26| `UserPromptSubmit` | User submits a prompt |28| :------------------- | :--------------------------------------------------- |

27| `PreToolUse` | Before tool execution |29| `SessionStart` | When a session begins or resumes |

28| `PermissionRequest` | When permission dialog appears |30| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |

29| `PostToolUse` | After tool succeeds |31| `PreToolUse` | Before a tool call executes. Can block it |

30| `PostToolUseFailure` | After tool fails |32| `PermissionRequest` | When a permission dialog appears |

31| `SubagentStart` | When spawning a subagent |33| `PostToolUse` | After a tool call succeeds |

32| `SubagentStop` | When subagent finishes |34| `PostToolUseFailure` | After a tool call fails |

33| `Stop` | Claude finishes responding |35| `Notification` | When Claude Code sends a notification |

36| `SubagentStart` | When a subagent is spawned |

37| `SubagentStop` | When a subagent finishes |

38| `Stop` | When Claude finishes responding |

34| `PreCompact` | Before context compaction |39| `PreCompact` | Before context compaction |

35| `SessionEnd` | Session terminates |40| `SessionEnd` | When a session terminates |

36| `Notification` | Claude Code sends notifications |41 

42### How a hook resolves

43 

44To see how these pieces fit together, consider this `PreToolUse` hook that blocks destructive shell commands. The hook runs `block-rm.sh` before every Bash tool call:

45 

46```json theme={null}

47{

48 "hooks": {

49 "PreToolUse": [

50 {

51 "matcher": "Bash",

52 "hooks": [

53 {

54 "type": "command",

55 "command": ".claude/hooks/block-rm.sh"

56 }

57 ]

58 }

59 ]

60 }

61}

62```

63 

64The script reads the JSON input from stdin, extracts the command, and blocks it if it contains `rm -rf`:

65 

66```bash theme={null}

67#!/bin/bash

68# .claude/hooks/block-rm.sh

69COMMAND=$(jq -r '.tool_input.command')

70 

71if echo "$COMMAND" | grep -q 'rm -rf'; then

72 echo '{"decision":"block","reason":"Destructive command blocked by hook"}'

73else

74 exit 0 # allow the command

75fi

76```

77 

78Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:

79 

80<Frame>

81 <img src="https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=7c13f51ffcbc37d22a593b27e2f2de72" alt="Hook resolution flow: PreToolUse event fires, matcher checks for Bash match, hook handler runs, result returns to Claude Code" data-og-width="780" width="780" data-og-height="290" height="290" data-path="images/hook-resolution.svg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=280&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=36a39a07e8bc1995dcb4639e09846905 280w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=560&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=6568d90c596c7605bbac2c325b0a0c86 560w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=840&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=255a6f68b9475a0e41dbde7b88002dad 840w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=1100&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=dcecf8d5edc88cd2bc49deb006d5760d 1100w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=1650&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=04fe51bf69ae375e9fd517f18674e35f 1650w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=2500&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=b1b76e0b77fddb5c7fa7bf302dacd80b 2500w" />

82</Frame>

83 

84<Steps>

85 <Step title="Event fires">

86 The `PreToolUse` event fires. Claude Code sends the tool input as JSON on stdin to the hook:

87 

88 ```json theme={null}

89 { "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }

90 ```

91 </Step>

92 

93 <Step title="Matcher checks">

94 The matcher `"Bash"` matches the tool name, so `block-rm.sh` runs. If you omit the matcher or use `"*"`, the hook runs on every occurrence of the event. Hooks only skip when a matcher is defined and doesn't match.

95 </Step>

96 

97 <Step title="Hook handler runs">

98 The script extracts `"rm -rf /tmp/build"` from the input and finds `rm -rf`, so it prints a decision to stdout:

99 

100 ```json theme={null}

101 { "decision": "block", "reason": "Destructive command blocked by hook" }

102 ```

103 

104 If the command had been safe (like `npm test`), the script would hit `exit 0` instead, which tells Claude Code to allow the tool call with no further action.

105 </Step>

106 

107 <Step title="Claude Code acts on the result">

108 Claude Code reads the JSON decision, blocks the tool call, and shows Claude the reason.

109 </Step>

110</Steps>

111 

112The [Configuration](#configuration) section below documents the full schema, and each [hook event](#hook-events) section documents what input your command receives and what output it can return.

37 113 

38## Configuration114## Configuration

39 115 

40Claude Code hooks are configured in your [settings files](/en/settings):116Hooks are defined in JSON settings files. The configuration has three levels of nesting:

41 117 

42* `~/.claude/settings.json` - User settings1181. Choose a [hook event](#hook-events) to respond to, like `PreToolUse` or `Stop`

43* `.claude/settings.json` - Project settings1192. Add a [matcher group](#matcher-patterns) to filter when it fires, like "only for the Bash tool"

44* `.claude/settings.local.json` - Local project settings (not committed)1203. Define one or more [hook handlers](#hook-handler-fields) to run when matched

45* Managed policy settings121 

122See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.

46 123 

47<Note>124<Note>

48 Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. See [Hook configuration](/en/settings#hook-configuration).125 This page uses specific terms for each level: **hook event** for the lifecycle point, **matcher group** for the filter, and **hook handler** for the shell command, prompt, or agent that runs. "Hook" on its own refers to the general feature.

49</Note>126</Note>

50 127 

51### Structure128### Hook locations

129 

130Where you define a hook determines its scope:

131 

132| Location | Scope | Shareable |

133| :--------------------------------------------------------- | :---------------------------- | :--------------------------------- |

134| `~/.claude/settings.json` | All your projects | No, local to your machine |

135| `.claude/settings.json` | Single project | Yes, can be committed to the repo |

136| `.claude/settings.local.json` | Single project | No, gitignored |

137| Managed policy settings | Organization-wide | Yes, admin-controlled |

138| [Plugin](/en/plugins) `hooks/hooks.json` | When plugin is enabled | Yes, bundled with the plugin |

139| [Skill](/en/skills) or [agent](/en/sub-agents) frontmatter | While the component is active | Yes, defined in the component file |

140 

141For details on settings file resolution, see [settings](/en/settings). Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. See [Hook configuration](/en/settings#hook-configuration).

52 142 

53Hooks are organized by matchers, where each matcher can have multiple hooks:143### Matcher patterns

144 

145The `matcher` field is a regex string that filters when hooks fire. Use `"*"`, `""`, or omit `matcher` entirely to match all occurrences. Each event type matches on a different field:

146 

147| Event | What the matcher filters | Example matcher values |

148| :--------------------------------------------------------------------- | :------------------------ | :----------------------------------------------------------------------------- |

149| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |

150| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |

151| `SessionEnd` | why the session ended | `clear`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |

152| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |

153| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |

154| `PreCompact` | what triggered compaction | `manual`, `auto` |

155| `SubagentStop` | agent type | same values as `SubagentStart` |

156| `UserPromptSubmit`, `Stop` | no matcher support | always fires on every occurrence |

157 

158The matcher is a regex, so `Edit|Write` matches either tool and `Notebook.*` matches any tool starting with Notebook. The matcher runs against a field from the [JSON input](#hook-input-and-output) that Claude Code sends to your hook on stdin. For tool events, that field is `tool_name`. Each [hook event](#hook-events) section lists the full set of matcher values and the input schema for that event.

159 

160This example runs a linting script only when Claude writes or edits a file:

54 161 

55```json theme={null}162```json theme={null}

56{163{

57 "hooks": {164 "hooks": {

58 "EventName": [165 "PostToolUse": [

59 {166 {

60 "matcher": "ToolPattern",167 "matcher": "Edit|Write",

61 "hooks": [168 "hooks": [

62 {169 {

63 "type": "command",170 "type": "command",

64 "command": "your-command-here"171 "command": "/path/to/lint-check.sh"

65 }172 }

66 ]173 ]

67 }174 }


70}177}

71```178```

72 179 

73* **matcher**: Pattern to match tool names, case-sensitive (only applicable for180`UserPromptSubmit` and `Stop` don't support matchers and always fire on every occurrence. If you add a `matcher` field to these events, it is silently ignored.

74 `PreToolUse`, `PermissionRequest`, and `PostToolUse`)181 

75 * Simple strings match exactly: `Write` matches only the Write tool182#### Match MCP tools

76 * Supports regex: `Edit|Write` or `Notebook.*`183 

77 * Use `*` to match all tools. You can also use empty string (`""`) or leave184[MCP](/en/mcp) server tools appear as regular tools in tool events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`), so you can match them the same way you match any other tool name.

78 `matcher` blank.185 

79* **hooks**: Array of hooks to execute when the pattern matches186MCP tools follow the naming pattern `mcp__<server>__<tool>`, for example:

80 * `type`: Hook execution type - `"command"` for bash commands or `"prompt"` for LLM-based evaluation187 

81 * `command`: (For `type: "command"`) The bash command to execute (can use `$CLAUDE_PROJECT_DIR` environment variable)188* `mcp__memory__create_entities`: Memory server's create entities tool

82 * `prompt`: (For `type: "prompt"`) The prompt to send to the LLM for evaluation189* `mcp__filesystem__read_file`: Filesystem server's read file tool

83 * `timeout`: (Optional) How long a hook should run, in seconds, before canceling that specific hook190* `mcp__github__search_repositories`: GitHub server's search tool

84 191 

85For events like `UserPromptSubmit`, `Stop`, `SubagentStop`, and `Setup`192Use regex patterns to target specific MCP tools or groups of tools:

86that don't use matchers, you can omit the matcher field:193 

194* `mcp__memory__.*` matches all tools from the `memory` server

195* `mcp__.*__write.*` matches any tool containing "write" from any server

196 

197This example logs all memory server operations and validates write operations from any MCP server:

87 198 

88```json theme={null}199```json theme={null}

89{200{

90 "hooks": {201 "hooks": {

91 "UserPromptSubmit": [202 "PreToolUse": [

203 {

204 "matcher": "mcp__memory__.*",

205 "hooks": [

92 {206 {

207 "type": "command",

208 "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"

209 }

210 ]

211 },

212 {

213 "matcher": "mcp__.*__write.*",

93 "hooks": [214 "hooks": [

94 {215 {

95 "type": "command",216 "type": "command",

96 "command": "/path/to/prompt-validator.py"217 "command": "/home/user/scripts/validate-mcp-write.py"

97 }218 }

98 ]219 ]

99 }220 }


102}223}

103```224```

104 225 

105### Project-Specific Hook Scripts226### Hook handler fields

106 227 

107You can use the environment variable `CLAUDE_PROJECT_DIR` (only available when228Each object in the inner `hooks` array is a hook handler: the shell command, LLM prompt, or agent that runs when the matcher matches. There are three types:

108Claude Code spawns the hook command) to reference scripts stored in your project,

109ensuring they work regardless of Claude's current directory:

110 229 

111```json theme={null}230* **[Command hooks](#command-hook-fields)** (`type: "command"`): run a shell command. Your script receives the event's [JSON input](#hook-input-and-output) on stdin and communicates results back through exit codes and stdout.

112{231* **[Prompt hooks](#prompt-and-agent-hook-fields)** (`type: "prompt"`): send a prompt to a Claude model for single-turn evaluation. The model returns a yes/no decision as JSON. See [Prompt-based hooks](#prompt-based-hooks).

232* **[Agent hooks](#prompt-and-agent-hook-fields)** (`type: "agent"`): spawn a subagent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. See [Agent-based hooks](#agent-based-hooks).

233 

234#### Common fields

235 

236These fields apply to all hook types:

237 

238| Field | Required | Description |

239| :-------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |

240| `type` | yes | `"command"`, `"prompt"`, or `"agent"` |

241| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |

242| `statusMessage` | no | Custom spinner message displayed while the hook runs |

243| `once` | no | If `true`, runs only once per session then is removed. Skills only, not agents. See [Hooks in skills and agents](#hooks-in-skills-and-agents) |

244 

245#### Command hook fields

246 

247In addition to the [common fields](#common-fields), command hooks accept these fields:

248 

249| Field | Required | Description |

250| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------ |

251| `command` | yes | Shell command to execute |

252| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |

253 

254#### Prompt and agent hook fields

255 

256In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:

257 

258| Field | Required | Description |

259| :------- | :------- | :------------------------------------------------------------------------------------------ |

260| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |

261| `model` | no | Model to use for evaluation. Defaults to a fast model |

262 

263All matching hooks run in parallel, and identical handlers are deduplicated automatically. Handlers run in the current directory with Claude Code's environment. The `$CLAUDE_CODE_REMOTE` environment variable is set to `"true"` in remote web environments and not set in the local CLI.

264 

265### Reference scripts by path

266 

267Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:

268 

269* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.

270* `${CLAUDE_PLUGIN_ROOT}`: the plugin's root directory, for scripts bundled with a [plugin](/en/plugins).

271 

272<Tabs>

273 <Tab title="Project scripts">

274 This example uses `$CLAUDE_PROJECT_DIR` to run a style checker from the project's `.claude/hooks/` directory after any `Write` or `Edit` tool call:

275 

276 ```json theme={null}

277 {

113 "hooks": {278 "hooks": {

114 "PostToolUse": [279 "PostToolUse": [

115 {280 {


123 }288 }

124 ]289 ]

125 }290 }

126}291 }

127```292 ```

128 293 </Tab>

129### Plugin hooks

130 

131[Plugins](/en/plugins) can provide hooks that integrate seamlessly with your user and project hooks. Plugin hooks are automatically merged with your configuration when plugins are enabled.

132 

133**How plugin hooks work**:

134 294 

135* Plugin hooks are defined in the plugin's `hooks/hooks.json` file or in a file given by a custom path to the `hooks` field.295 <Tab title="Plugin scripts">

136* When a plugin is enabled, its hooks are merged with user and project hooks296 Define plugin hooks in `hooks/hooks.json` with an optional top-level `description` field. When a plugin is enabled, its hooks merge with your user and project hooks.

137* Multiple hooks from different sources can respond to the same event

138* Plugin hooks use the `${CLAUDE_PLUGIN_ROOT}` environment variable to reference plugin files

139 297 

140**Example plugin hook configuration**:298 This example runs a formatting script bundled with the plugin:

141 299 

142```json theme={null}300 ```json theme={null}

143{301 {

144 "description": "Automatic code formatting",302 "description": "Automatic code formatting",

145 "hooks": {303 "hooks": {

146 "PostToolUse": [304 "PostToolUse": [


156 }314 }

157 ]315 ]

158 }316 }

159}317 }

160```318 ```

161 

162<Note>

163 Plugin hooks use the same format as regular hooks with an optional `description` field to explain the hook's purpose.

164</Note>

165 

166<Note>

167 Plugin hooks run alongside your custom hooks. If multiple hooks match an event, they all execute in parallel.

168</Note>

169 

170**Environment variables for plugins**:

171 

172* `${CLAUDE_PLUGIN_ROOT}`: Absolute path to the plugin directory

173* `${CLAUDE_PROJECT_DIR}`: Project root directory (same as for project hooks)

174* All standard environment variables are available

175 319 

176See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.320 See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.

321 </Tab>

322</Tabs>

177 323 

178### Hooks in skills and agents324### Hooks in skills and agents

179 325 

180In addition to settings files and plugins, hooks can be defined directly in [skills](/en/skills) and [subagents](/en/sub-agents) using frontmatter. These hooks are scoped to the component's lifecycle and only run when that component is active.326In addition to settings files and plugins, hooks can be defined directly in [skills](/en/skills) and [subagents](/en/sub-agents) using frontmatter. These hooks are scoped to the component's lifecycle and only run when that component is active.

181 327 

182**Supported events**: `PreToolUse`, `PostToolUse`, and `Stop`328All hook events are supported. For subagents, `Stop` hooks are automatically converted to `SubagentStop` since that is the event that fires when a subagent completes.

183 329 

184**Example in a Skill**:330Hooks use the same configuration format as settings-based hooks but are scoped to the component's lifetime and cleaned up when it finishes.

331 

332This skill defines a `PreToolUse` hook that runs a security validation script before each `Bash` command:

185 333 

186```yaml theme={null}334```yaml theme={null}

187---335---


196---344---

197```345```

198 346 

199**Example in an agent**:347Agents use the same format in their YAML frontmatter.

200 

201```yaml theme={null}

202name: code-reviewer

203description: Review code changes

204hooks:

205 PostToolUse:

206 - matcher: "Edit|Write"

207 hooks:

208 - type: command

209 command: "./scripts/run-linter.sh"

210```

211 

212Component-scoped hooks follow the same configuration format as settings-based hooks but are automatically cleaned up when the component finishes executing.

213 348 

214**Additional option for skills:**349### The `/hooks` menu

215 

216* `once`: Set to `true` to run the hook only once per session. After the first successful execution, the hook is removed. Note: This option is currently only supported for skills, not for agents.

217 

218## Prompt-Based Hooks

219 

220In addition to bash command hooks (`type: "command"`), Claude Code supports prompt-based hooks (`type: "prompt"`) that use an LLM to evaluate whether to allow or block an action. Prompt-based hooks are currently only supported for `Stop` and `SubagentStop` hooks, where they enable intelligent, context-aware decisions.

221 

222### How prompt-based hooks work

223 

224Instead of executing a bash command, prompt-based hooks:

225 

2261. Send the hook input and your prompt to a fast LLM (Haiku)

2272. The LLM responds with structured JSON containing a decision

2283. Claude Code processes the decision automatically

229 

230### Configuration

231 

232```json theme={null}

233{

234 "hooks": {

235 "Stop": [

236 {

237 "hooks": [

238 {

239 "type": "prompt",

240 "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."

241 }

242 ]

243 }

244 ]

245 }

246}

247```

248 350 

249**Fields:**351Type `/hooks` in Claude Code to open the interactive hooks manager, where you can view, add, and delete hooks without editing settings files directly. For a step-by-step walkthrough, see [Set up your first hook](/en/hooks-guide#set-up-your-first-hook) in the guide.

250 352 

251* `type`: Must be `"prompt"`353Each hook in the menu is labeled with a bracket prefix indicating its source:

252* `prompt`: The prompt text to send to the LLM

253 * Use `$ARGUMENTS` as a placeholder for the hook input JSON

254 * If `$ARGUMENTS` is not present, input JSON is appended to the prompt

255* `timeout`: (Optional) Timeout in seconds (default: 30 seconds)

256 354 

257### Response schema355* `[User]`: from `~/.claude/settings.json`

356* `[Project]`: from `.claude/settings.json`

357* `[Local]`: from `.claude/settings.local.json`

358* `[Plugin]`: from a plugin's `hooks/hooks.json`, read-only

258 359 

259The LLM must respond with JSON containing:360### Disable or remove hooks

260 361 

261```json theme={null}362To remove a hook, delete its entry from the settings JSON file, or use the `/hooks` menu and select the hook to delete it.

262{

263 "ok": true | false,

264 "reason": "Explanation for the decision"

265}

266```

267 363 

268**Response fields:**364To temporarily disable all hooks without removing them, set `"disableAllHooks": true` in your settings file or use the toggle in the `/hooks` menu. There is no way to disable an individual hook while keeping it in the configuration.

269 365 

270* `ok`: `true` allows the action, `false` prevents it366Direct edits to hooks in settings files don't take effect immediately. Claude Code captures a snapshot of hooks at startup and uses it throughout the session. This prevents malicious or accidental hook modifications from taking effect mid-session without your review. If hooks are modified externally, Claude Code warns you and requires review in the `/hooks` menu before changes apply.

271* `reason`: Required when `ok` is `false`. Explanation shown to Claude

272 367 

273### Supported hook events368## Hook input and output

274 369 

275Prompt-based hooks work with any hook event, but are most useful for:370Hooks receive JSON data via stdin and communicate results through exit codes, stdout, and stderr. This section covers fields and behavior common to all events. Each event's section under [Hook events](#hook-events) includes its specific input schema and decision control options.

276 371 

277* **Stop**: Intelligently decide if Claude should continue working372### Common input fields

278* **SubagentStop**: Evaluate if a subagent has completed its task

279* **UserPromptSubmit**: Validate user prompts with LLM assistance

280* **PreToolUse**: Make context-aware permission decisions

281* **PermissionRequest**: Intelligently allow or deny permission dialogs

282 373 

283### Example: Intelligent Stop hook374All hook events receive these fields via stdin as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section:

284 375 

285```json theme={null}376| Field | Description |

286{377| :---------------- | :--------------------------------------------------------------------------------------------------------------------------------- |

287 "hooks": {378| `session_id` | Current session identifier |

288 "Stop": [379| `transcript_path` | Path to conversation JSON |

289 {380| `cwd` | Current working directory when the hook is invoked |

290 "hooks": [381| `permission_mode` | Current [permission mode](/en/iam#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"dontAsk"`, or `"bypassPermissions"` |

291 {382| `hook_event_name` | Name of the event that fired |

292 "type": "prompt",

293 "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",

294 "timeout": 30

295 }

296 ]

297 }

298 ]

299 }

300}

301```

302 383 

303### Example: SubagentStop with custom logic384For example, a `PreToolUse` hook for a Bash command receives this on stdin:

304 385 

305```json theme={null}386```json theme={null}

306{387{

307 "hooks": {388 "session_id": "abc123",

308 "SubagentStop": [389 "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",

309 {390 "cwd": "/home/user/my-project",

310 "hooks": [391 "permission_mode": "default",

311 {392 "hook_event_name": "PreToolUse",

312 "type": "prompt",393 "tool_name": "Bash",

313 "prompt": "Evaluate if this subagent should stop. Input: $ARGUMENTS\n\nCheck if:\n- The subagent completed its assigned task\n- Any errors occurred that need fixing\n- Additional context gathering is needed\n\nReturn: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"explanation\"} to continue."394 "tool_input": {

314 }395 "command": "npm test"

315 ]

316 }

317 ]

318 }396 }

319}397}

320```398```

321 399 

322### Comparison with bash command hooks400The `tool_name` and `tool_input` fields are event-specific. Each [hook event](#hook-events) section documents the additional fields for that event.

323 401 

324| Feature | Bash Command Hooks | Prompt-Based Hooks |402### Exit code output

325| --------------------- | ----------------------- | ------------------------------ |

326| **Execution** | Runs bash script | Queries LLM |

327| **Decision logic** | You implement in code | LLM evaluates context |

328| **Setup complexity** | Requires script file | Configure prompt |

329| **Context awareness** | Limited to script logic | Natural language understanding |

330| **Performance** | Fast (local execution) | Slower (API call) |

331| **Use case** | Deterministic rules | Context-aware decisions |

332 403 

333### Best practices404The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.

334 405 

335* **Be specific in prompts**: Clearly state what you want the LLM to evaluate406**Exit 0** means success. Claude Code parses stdout for [JSON output fields](#json-output) like `decision` or `reason`. JSON output is only processed on exit 0. For most events, stdout is only shown in verbose mode (`Ctrl+O`). The exceptions are `UserPromptSubmit` and `SessionStart`, where stdout is added as context that Claude can see and act on.

336* **Include decision criteria**: List the factors the LLM should consider

337* **Test your prompts**: Verify the LLM makes correct decisions for your use cases

338* **Set appropriate timeouts**: Default is 30 seconds, adjust if needed

339* **Use for complex decisions**: Bash hooks are better for simple, deterministic rules

340 407 

341See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.408**Exit 2** means a blocking error. Claude Code ignores stdout and any JSON in it. Instead, stderr text is fed back to Claude as an error message. The effect depends on the event: `PreToolUse` blocks the tool call, `UserPromptSubmit` rejects the prompt, and so on. See [exit code 2 behavior](#exit-code-2-behavior-per-event) for the full list.

342 409 

343## Hook Events410**Any other exit code** is a non-blocking error. stderr is shown in verbose mode (`Ctrl+O`) and execution continues.

344 411 

345### PreToolUse412For example, a hook command script that blocks dangerous Bash commands:

346 413 

347Runs after Claude creates tool parameters and before processing the tool call.414```bash theme={null}

415#!/bin/bash

416# Reads JSON input from stdin, checks the command

417command=$(jq -r '.tool_input.command' < /dev/stdin)

348 418 

349**Common matchers:**419if [[ "$command" == rm* ]]; then

420 echo "Blocked: rm commands are not allowed" >&2

421 exit 2 # Blocking error: tool call is prevented

422fi

350 423 

351* `Task` - Subagent tasks (see [subagents documentation](/en/sub-agents))424exit 0 # Success: tool call proceeds

352* `Bash` - Shell commands425```

353* `Glob` - File pattern matching

354* `Grep` - Content search

355* `Read` - File reading

356* `Edit` - File editing

357* `Write` - File writing

358* `WebFetch`, `WebSearch` - Web operations

359 426 

360Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.427#### Exit code 2 behavior per event

361 428 

362### PermissionRequest429Exit code 2 is the way a hook signals "stop, don't do this." The effect depends on the event, because some events represent actions that can be blocked (like a tool call that hasn't happened yet) and others represent things that already happened or can't be prevented.

363 430 

364Runs when the user is shown a permission dialog.431| Hook event | Can block? | What happens on exit 2 |

365Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.432| :------------------- | :--------- | :-------------------------------------------------------- |

433| `PreToolUse` | Yes | Blocks the tool call |

434| `PermissionRequest` | Yes | Denies the permission |

435| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |

436| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |

437| `SubagentStop` | Yes | Prevents the subagent from stopping |

438| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |

439| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |

440| `Notification` | No | Shows stderr to user only |

441| `SubagentStart` | No | Shows stderr to user only |

442| `SessionStart` | No | Shows stderr to user only |

443| `SessionEnd` | No | Shows stderr to user only |

444| `PreCompact` | No | Shows stderr to user only |

366 445 

367Recognizes the same matcher values as PreToolUse.446### JSON output

368 447 

369### PostToolUse448You must choose one approach per hook, not both: either use exit codes alone for signaling, or exit 0 and print JSON for structured control. Claude Code only processes JSON on exit 0. If you exit 2, any JSON is ignored.

370 449 

371Runs immediately after a tool completes successfully.450Instead of relying on exit codes alone, hooks can print JSON to stdout on exit 0. Claude Code reads specific fields from this JSON to decide what to do next.

372 451 

373Recognizes the same matcher values as PreToolUse.452Your hook's stdout must contain only the JSON object. If your shell profile prints text on startup, it can interfere with JSON parsing. See [JSON validation failed](/en/hooks-guide#json-validation-failed) in the troubleshooting guide.

374 453 

375### Notification454The JSON object has two parts:

376 455 

377Runs when Claude Code sends notifications. Supports matchers to filter by notification type.456* **Top-level fields** like `continue` and `decision` work across all events. These are listed in the table below.

457* **`hookSpecificOutput`** is a nested object for event-specific fields like `permissionDecision` or `additionalContext`. It requires a `hookEventName` field set to the event name, like `"PreToolUse"` or `"Stop"`. Each event's decision control section under [Hook events](#hook-events) documents what fields go here.

378 458 

379**Common matchers:**459| Field | Default | Description |

460| :--------------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------- |

461| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over event-specific fields like `decision` or `permissionDecision` |

462| `stopReason` | none | Message shown to the user when `continue` is `false`. Not shown to Claude |

463| `suppressOutput` | `false` | If `true`, hides stdout from verbose mode output |

464| `systemMessage` | none | Warning message shown to the user |

380 465 

381* `permission_prompt` - Permission requests from Claude Code466This example uses a top-level field to stop Claude:

382* `idle_prompt` - When Claude is waiting for user input (after 60+ seconds of idle time)

383* `auth_success` - Authentication success notifications

384* `elicitation_dialog` - When Claude Code needs input for MCP tool elicitation

385 467 

386You can use matchers to run different hooks for different notification types, or omit the matcher to run hooks for all notifications.468```json theme={null}

469{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }

470```

387 471 

388**Example: Different notifications for different types**472This example uses `hookSpecificOutput` to deny a PreToolUse tool call:

389 473 

390```json theme={null}474```json theme={null}

391{475{

392 "hooks": {476 "hookSpecificOutput": {

393 "Notification": [477 "hookEventName": "PreToolUse",

394 {478 "permissionDecision": "deny",

395 "matcher": "permission_prompt",479 "permissionDecisionReason": "Database writes are not allowed"

396 "hooks": [

397 {

398 "type": "command",

399 "command": "/path/to/permission-alert.sh"

400 }

401 ]

402 },

403 {

404 "matcher": "idle_prompt",

405 "hooks": [

406 {

407 "type": "command",

408 "command": "/path/to/idle-notification.sh"

409 }

410 ]

411 }

412 ]

413 }480 }

414}481}

415```482```

416 483 

417### UserPromptSubmit484For extended examples including Bash command validation, prompt filtering, and auto-approval scripts, see [What you can automate](/en/hooks-guide#what-you-can-automate) in the guide and the [Bash command validator reference implementation](https://github.com/anthropics/claude-code/blob/main/examples/hooks/bash_command_validator_example.py).

418 

419Runs when the user submits a prompt, before Claude processes it. This allows you

420to add additional context based on the prompt/conversation, validate prompts, or

421block certain types of prompts.

422 

423### Stop

424 

425Runs when the main Claude Code agent has finished responding. Does not run if

426the stoppage occurred due to a user interrupt.

427 

428### SubagentStop

429 

430Runs when a Claude Code subagent (Task tool call) has finished responding.

431 485 

432### PreCompact486## Hook events

433 

434Runs before Claude Code is about to run a compact operation.

435 487 

436**Matchers:**488Each event corresponds to a point in Claude Code's lifecycle where hooks can run. The sections below are ordered to match the lifecycle: from session setup through the agentic loop to session end. Each section describes when the event fires, what matchers it supports, the JSON input it receives, and how to control behavior through output.

437 489 

438* `manual` - Invoked from `/compact`490### SessionStart

439* `auto` - Invoked from auto-compact (due to full context window)

440 491 

441### Setup492Runs when Claude Code starts a new session or resumes an existing session. Useful for loading development context like existing issues or recent changes to your codebase, or setting up environment variables. For static context that does not require a script, use [CLAUDE.md](/en/memory) instead.

442 493 

443Runs when Claude Code is invoked with repository setup and maintenance flags (`--init`, `--init-only`, or `--maintenance`). Use this hook for operations you don't want on every session—such as installing dependencies, running migrations, or periodic maintenance tasks.494SessionStart runs on every session, so keep these hooks fast.

444 495 

445<Note>496The matcher value corresponds to how the session was initiated:

446 Use **Setup** hooks for one-time or occasional operations (dependency installation, migrations, cleanup). Use **SessionStart** hooks for things you want on every session (loading context, setting environment variables). Setup hooks require explicit flags because running them automatically would slow down every session start.

447</Note>

448 497 

449**Matchers:**498| Matcher | When it fires |

499| :-------- | :------------------------------------- |

500| `startup` | New session |

501| `resume` | `--resume`, `--continue`, or `/resume` |

502| `clear` | `/clear` |

503| `compact` | Auto or manual compaction |

450 504 

451* `init` - Invoked from `--init` or `--init-only` flags505#### SessionStart input

452* `maintenance` - Invoked from `--maintenance` flag

453 506 

454Setup hooks have access to the `CLAUDE_ENV_FILE` environment variable for persisting environment variables, similar to SessionStart hooks.507In addition to the [common input fields](#common-input-fields), SessionStart hooks receive `source`, `model`, and optionally `agent_type`. The `source` field indicates how the session started: `"startup"` for new sessions, `"resume"` for resumed sessions, `"clear"` after `/clear`, or `"compact"` after compaction. The `model` field contains the model identifier. If you start Claude Code with `claude --agent <name>`, an `agent_type` field contains the agent name.

455 508 

456### SessionStart509```json theme={null}

510{

511 "session_id": "abc123",

512 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",

513 "cwd": "/Users/...",

514 "permission_mode": "default",

515 "hook_event_name": "SessionStart",

516 "source": "startup",

517 "model": "claude-sonnet-4-5-20250929"

518}

519```

457 520 

458Runs when Claude Code starts a new session or resumes an existing session (which521#### SessionStart decision control

459currently does start a new session under the hood). Useful for loading development context like existing issues or recent changes to your codebase, or setting up environment variables.

460 522 

461<Note>523Any text your hook script prints to stdout is added as context for Claude. In addition to the [JSON output fields](#json-output) available to all hooks, you can return these event-specific fields:

462 For one-time operations like installing dependencies or running migrations, use [Setup hooks](#setup) instead. SessionStart runs on every session, so keep these hooks fast.

463</Note>

464 524 

465**Matchers:**525| Field | Description |

526| :------------------ | :------------------------------------------------------------------------ |

527| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |

466 528 

467* `startup` - Invoked from startup529```json theme={null}

468* `resume` - Invoked from `--resume`, `--continue`, or `/resume`530{

469* `clear` - Invoked from `/clear`531 "hookSpecificOutput": {

470* `compact` - Invoked from auto or manual compact.532 "hookEventName": "SessionStart",

533 "additionalContext": "My additional context here"

534 }

535}

536```

471 537 

472#### Persisting environment variables538#### Persist environment variables

473 539 

474SessionStart hooks have access to the `CLAUDE_ENV_FILE` environment variable, which provides a file path where you can persist environment variables for subsequent bash commands.540SessionStart hooks have access to the `CLAUDE_ENV_FILE` environment variable, which provides a file path where you can persist environment variables for subsequent Bash commands.

475 541 

476**Example: Setting individual environment variables**542To set individual environment variables, write `export` statements to `CLAUDE_ENV_FILE`. Use append (`>>`) to preserve variables set by other hooks:

477 543 

478```bash theme={null}544```bash theme={null}

479#!/bin/bash545#!/bin/bash

480 546 

481if [ -n "$CLAUDE_ENV_FILE" ]; then547if [ -n "$CLAUDE_ENV_FILE" ]; then

482 echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"548 echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"

483 echo 'export API_KEY=your-api-key' >> "$CLAUDE_ENV_FILE"549 echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"

484 echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"550 echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"

485fi551fi

486 552 

487exit 0553exit 0

488```554```

489 555 

490**Example: Persisting all environment changes from the hook**556To capture all environment changes from setup commands, compare the exported variables before and after:

491 

492When your setup modifies the environment (for example, `nvm use`), capture and persist all changes by diffing the environment:

493 557 

494```bash theme={null}558```bash theme={null}

495#!/bin/bash559#!/bin/bash


510exit 0572exit 0

511```573```

512 574 

513Any variables written to this file will be available in all subsequent bash commands that Claude Code executes during the session.575Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.

514 576 

515<Note>577<Note>

516 `CLAUDE_ENV_FILE` is only available for SessionStart hooks. Other hook types do not have access to this variable.578 `CLAUDE_ENV_FILE` is available for SessionStart hooks. Other hook types do not have access to this variable.

517</Note>579</Note>

518 580 

519### SessionEnd581### UserPromptSubmit

520 

521Runs when a Claude Code session ends. Useful for cleanup tasks, logging session

522statistics, or saving session state.

523 

524The `reason` field in the hook input will be one of:

525 582 

526* `clear` - Session cleared with /clear command583Runs when the user submits a prompt, before Claude processes it. This allows you

527* `logout` - User logged out584to add additional context based on the prompt/conversation, validate prompts, or

528* `prompt_input_exit` - User exited while prompt input was visible585block certain types of prompts.

529* `other` - Other exit reasons

530 586 

531## Hook Input587#### UserPromptSubmit input

532 588 

533Hooks receive JSON data via stdin containing session information and589In addition to the [common input fields](#common-input-fields), UserPromptSubmit hooks receive the `prompt` field containing the text the user submitted.

534event-specific data:

535 590 

536```typescript theme={null}591```json theme={null}

537{592{

538 // Common fields593 "session_id": "abc123",

539 session_id: string594 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",

540 transcript_path: string // Path to conversation JSON595 "cwd": "/Users/...",

541 cwd: string // The current working directory when the hook is invoked596 "permission_mode": "default",

542 permission_mode: string // Current permission mode: "default", "plan", "acceptEdits", "dontAsk", or "bypassPermissions"597 "hook_event_name": "UserPromptSubmit",

543 598 "prompt": "Write a function to calculate the factorial of a number"

544 // Event-specific fields

545 hook_event_name: string

546 ...

547}599}

548```600```

549 601 

550### PreToolUse Input602#### UserPromptSubmit decision control

603 

604`UserPromptSubmit` hooks can control whether a user prompt is processed and add context. All [JSON output fields](#json-output) are available.

605 

606There are two ways to add context to the conversation on exit code 0:

607 

608* **Plain text stdout**: any non-JSON text written to stdout is added as context

609* **JSON with `additionalContext`**: use the JSON format below for more control. The `additionalContext` field is added as context

551 610 

552The exact schema for `tool_input` depends on the tool. Here are examples for commonly hooked tools.611Plain stdout is shown as hook output in the transcript. The `additionalContext` field is added more discretely.

553 612 

554#### Bash tool613To block a prompt, return a JSON object with `decision` set to `"block"`:

555 614 

556The Bash tool is the most commonly hooked tool for command validation:615| Field | Description |

616| :------------------ | :----------------------------------------------------------------------------------------------------------------- |

617| `decision` | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |

618| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |

619| `additionalContext` | String added to Claude's context |

557 620 

558```json theme={null}621```json theme={null}

559{622{

560 "session_id": "abc123",623 "decision": "block",

561 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",624 "reason": "Explanation for decision",

562 "cwd": "/Users/...",625 "hookSpecificOutput": {

563 "permission_mode": "default",626 "hookEventName": "UserPromptSubmit",

564 "hook_event_name": "PreToolUse",627 "additionalContext": "My additional context here"

565 "tool_name": "Bash",628 }

566 "tool_input": {

567 "command": "psql -c 'SELECT * FROM users'",

568 "description": "Query the users table",

569 "timeout": 120000

570 },

571 "tool_use_id": "toolu_01ABC123..."

572}629}

573```630```

574 631 

575| Field | Type | Description |632<Note>

576| :------------------ | :------ | :-------------------------------------------- |633 The JSON format isn't required for simple use cases. To add context, you can print plain text to stdout with exit code 0. Use JSON when you need to

577| `command` | string | The shell command to execute |634 block prompts or want more structured control.

578| `description` | string | Optional description of what the command does |635</Note>

579| `timeout` | number | Optional timeout in milliseconds |636 

580| `run_in_background` | boolean | Whether to run the command in background |637### PreToolUse

638 

639Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name: `Bash`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Task`, `WebFetch`, `WebSearch`, and any [MCP tool names](#match-mcp-tools).

640 

641Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.

642 

643#### PreToolUse input

644 

645In addition to the [common input fields](#common-input-fields), PreToolUse hooks receive `tool_name`, `tool_input`, and `tool_use_id`. The `tool_input` fields depend on the tool:

646 

647##### Bash

648 

649Executes shell commands.

650 

651| Field | Type | Example | Description |

652| :------------------ | :------ | :----------------- | :-------------------------------------------- |

653| `command` | string | `"npm test"` | The shell command to execute |

654| `description` | string | `"Run test suite"` | Optional description of what the command does |

655| `timeout` | number | `120000` | Optional timeout in milliseconds |

656| `run_in_background` | boolean | `false` | Whether to run the command in background |

657 

658##### Write

659 

660Creates or overwrites a file.

661 

662| Field | Type | Example | Description |

663| :---------- | :----- | :-------------------- | :--------------------------------- |

664| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to write |

665| `content` | string | `"file content"` | Content to write to the file |

666 

667##### Edit

668 

669Replaces a string in an existing file.

670 

671| Field | Type | Example | Description |

672| :------------ | :------ | :-------------------- | :--------------------------------- |

673| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to edit |

674| `old_string` | string | `"original text"` | Text to find and replace |

675| `new_string` | string | `"replacement text"` | Replacement text |

676| `replace_all` | boolean | `false` | Whether to replace all occurrences |

677 

678##### Read

679 

680Reads file contents.

581 681 

582#### Write tool682| Field | Type | Example | Description |

683| :---------- | :----- | :-------------------- | :----------------------------------------- |

684| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to read |

685| `offset` | number | `10` | Optional line number to start reading from |

686| `limit` | number | `50` | Optional number of lines to read |

687 

688##### Glob

689 

690Finds files matching a glob pattern.

691 

692| Field | Type | Example | Description |

693| :-------- | :----- | :--------------- | :--------------------------------------------------------------------- |

694| `pattern` | string | `"**/*.ts"` | Glob pattern to match files against |

695| `path` | string | `"/path/to/dir"` | Optional directory to search in. Defaults to current working directory |

696 

697##### Grep

698 

699Searches file contents with regular expressions.

700 

701| Field | Type | Example | Description |

702| :------------ | :------ | :--------------- | :------------------------------------------------------------------------------------ |

703| `pattern` | string | `"TODO.*fix"` | Regular expression pattern to search for |

704| `path` | string | `"/path/to/dir"` | Optional file or directory to search in |

705| `glob` | string | `"*.ts"` | Optional glob pattern to filter files |

706| `output_mode` | string | `"content"` | `"content"`, `"files_with_matches"`, or `"count"`. Defaults to `"files_with_matches"` |

707| `-i` | boolean | `true` | Case insensitive search |

708| `multiline` | boolean | `false` | Enable multiline matching |

709 

710##### WebFetch

711 

712Fetches and processes web content.

713 

714| Field | Type | Example | Description |

715| :------- | :----- | :---------------------------- | :----------------------------------- |

716| `url` | string | `"https://example.com/api"` | URL to fetch content from |

717| `prompt` | string | `"Extract the API endpoints"` | Prompt to run on the fetched content |

718 

719##### WebSearch

720 

721Searches the web.

722 

723| Field | Type | Example | Description |

724| :---------------- | :----- | :----------------------------- | :------------------------------------------------ |

725| `query` | string | `"react hooks best practices"` | Search query |

726| `allowed_domains` | array | `["docs.example.com"]` | Optional: only include results from these domains |

727| `blocked_domains` | array | `["spam.example.com"]` | Optional: exclude results from these domains |

728 

729##### Task

730 

731Spawns a [subagent](/en/sub-agents).

732 

733| Field | Type | Example | Description |

734| :-------------- | :----- | :------------------------- | :------------------------------------------- |

735| `prompt` | string | `"Find all API endpoints"` | The task for the agent to perform |

736| `description` | string | `"Find API endpoints"` | Short description of the task |

737| `subagent_type` | string | `"Explore"` | Type of specialized agent to use |

738| `model` | string | `"sonnet"` | Optional model alias to override the default |

739 

740#### PreToolUse decision control

741 

742`PreToolUse` hooks can control whether a tool call proceeds. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return a `hookSpecificOutput` object with these event-specific fields:

743 

744| Field | Description |

745| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |

746| `permissionDecision` | `"allow"` bypasses the permission system, `"deny"` prevents the tool call, `"ask"` prompts the user to confirm |

747| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude |

748| `updatedInput` | Modifies the tool's input parameters before execution. Combine with `"allow"` to auto-approve, or `"ask"` to show the modified input to the user |

749| `additionalContext` | String added to Claude's context before the tool executes |

583 750 

584```json theme={null}751```json theme={null}

585{752{

586 "session_id": "abc123",753 "hookSpecificOutput": {

587 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",754 "hookEventName": "PreToolUse",

588 "cwd": "/Users/...",755 "permissionDecision": "allow",

589 "permission_mode": "default",756 "permissionDecisionReason": "My reason here",

590 "hook_event_name": "PreToolUse",757 "updatedInput": {

591 "tool_name": "Write",758 "field_to_modify": "new value"

592 "tool_input": {

593 "file_path": "/path/to/file.txt",

594 "content": "file content"

595 },759 },

596 "tool_use_id": "toolu_01ABC123..."760 "additionalContext": "Current environment: production. Proceed with caution."

761 }

597}762}

598```763```

599 764 

600| Field | Type | Description |765<Note>

601| :---------- | :----- | :--------------------------------- |766 The `decision` and `reason` fields are deprecated for PreToolUse hooks.

602| `file_path` | string | Absolute path to the file to write |767 Use `hookSpecificOutput.permissionDecision` and

603| `content` | string | Content to write to the file |768 `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields

769 `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.

770</Note>

771 

772### PermissionRequest

773 

774Runs when the user is shown a permission dialog.

775Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.

776 

777Matches on tool name, same values as PreToolUse.

604 778 

605#### Edit tool779#### PermissionRequest input

780 

781PermissionRequest hooks receive `tool_name` and `tool_input` fields like PreToolUse hooks, but without `tool_use_id`. An optional `permission_suggestions` array contains the "always allow" options the user would normally see in the permission dialog. The difference is when the hook fires: PermissionRequest hooks run when a permission dialog is about to be shown to the user, while PreToolUse hooks run before tool execution regardless of permission status.

606 782 

607```json theme={null}783```json theme={null}

608{784{


610 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",786 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",

611 "cwd": "/Users/...",787 "cwd": "/Users/...",

612 "permission_mode": "default",788 "permission_mode": "default",

613 "hook_event_name": "PreToolUse",789 "hook_event_name": "PermissionRequest",

614 "tool_name": "Edit",790 "tool_name": "Bash",

615 "tool_input": {791 "tool_input": {

616 "file_path": "/path/to/file.txt",792 "command": "rm -rf node_modules",

617 "old_string": "original text",793 "description": "Remove node_modules directory"

618 "new_string": "replacement text"

619 },794 },

620 "tool_use_id": "toolu_01ABC123..."795 "permission_suggestions": [

796 { "type": "toolAlwaysAllow", "tool": "Bash" }

797 ]

621}798}

622```799```

623 800 

624| Field | Type | Description |801#### PermissionRequest decision control

625| :------------ | :------ | :-------------------------------------------------- |

626| `file_path` | string | Absolute path to the file to edit |

627| `old_string` | string | Text to find and replace |

628| `new_string` | string | Replacement text |

629| `replace_all` | boolean | Whether to replace all occurrences (default: false) |

630 802 

631#### Read tool803`PermissionRequest` hooks can allow or deny permission requests. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return a `decision` object with these event-specific fields:

804 

805| Field | Description |

806| :------------------- | :------------------------------------------------------------------------------------------------------------- |

807| `behavior` | `"allow"` grants the permission, `"deny"` denies it |

808| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution |

809| `updatedPermissions` | For `"allow"` only: applies permission rule updates, equivalent to the user selecting an "always allow" option |

810| `message` | For `"deny"` only: tells Claude why the permission was denied |

811| `interrupt` | For `"deny"` only: if `true`, stops Claude |

632 812 

633```json theme={null}813```json theme={null}

634{814{

635 "session_id": "abc123",815 "hookSpecificOutput": {

636 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",816 "hookEventName": "PermissionRequest",

637 "cwd": "/Users/...",817 "decision": {

638 "permission_mode": "default",818 "behavior": "allow",

639 "hook_event_name": "PreToolUse",819 "updatedInput": {

640 "tool_name": "Read",820 "command": "npm run lint"

641 "tool_input": {821 }

642 "file_path": "/path/to/file.txt"822 }

643 },823 }

644 "tool_use_id": "toolu_01ABC123..."

645}824}

646```825```

647 826 

648| Field | Type | Description |827### PostToolUse

649| :---------- | :----- | :----------------------------------------- |828 

650| `file_path` | string | Absolute path to the file to read |829Runs immediately after a tool completes successfully.

651| `offset` | number | Optional line number to start reading from |830 

652| `limit` | number | Optional number of lines to read |831Matches on tool name, same values as PreToolUse.

653 832 

654### PostToolUse Input833#### PostToolUse input

655 834 

656The exact schema for `tool_input` and `tool_response` depends on the tool.835`PostToolUse` hooks fire after a tool has already executed successfully. The input includes both `tool_input`, the arguments sent to the tool, and `tool_response`, the result it returned. The exact schema for both depends on the tool.

657 836 

658```json theme={null}837```json theme={null}

659{838{


675}854}

676```855```

677 856 

678### Notification Input857#### PostToolUse decision control

858 

859`PostToolUse` hooks can provide feedback to Claude after tool execution. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:

860 

861| Field | Description |

862| :--------------------- | :----------------------------------------------------------------------------------------- |

863| `decision` | `"block"` prompts Claude with the `reason`. Omit to allow the action to proceed |

864| `reason` | Explanation shown to Claude when `decision` is `"block"` |

865| `additionalContext` | Additional context for Claude to consider |

866| `updatedMCPToolOutput` | For [MCP tools](#match-mcp-tools) only: replaces the tool's output with the provided value |

679 867 

680```json theme={null}868```json theme={null}

681{869{

682 "session_id": "abc123",870 "decision": "block",

683 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",871 "reason": "Explanation for decision",

684 "cwd": "/Users/...",872 "hookSpecificOutput": {

685 "permission_mode": "default",873 "hookEventName": "PostToolUse",

686 "hook_event_name": "Notification",874 "additionalContext": "Additional information for Claude"

687 "message": "Claude needs your permission to use Bash",875 }

688 "notification_type": "permission_prompt"

689}876}

690```877```

691 878 

692### UserPromptSubmit Input879### PostToolUseFailure

880 

881Runs when a tool execution fails. This event fires for tool calls that throw errors or return failure results. Use this to log failures, send alerts, or provide corrective feedback to Claude.

882 

883Matches on tool name, same values as PreToolUse.

884 

885#### PostToolUseFailure input

886 

887PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as PostToolUse, along with error information as top-level fields:

693 888 

694```json theme={null}889```json theme={null}

695{890{


697 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",892 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",

698 "cwd": "/Users/...",893 "cwd": "/Users/...",

699 "permission_mode": "default",894 "permission_mode": "default",

700 "hook_event_name": "UserPromptSubmit",895 "hook_event_name": "PostToolUseFailure",

701 "prompt": "Write a function to calculate the factorial of a number"896 "tool_name": "Bash",

897 "tool_input": {

898 "command": "npm test",

899 "description": "Run test suite"

900 },

901 "tool_use_id": "toolu_01ABC123...",

902 "error": "Command exited with non-zero status code 1",

903 "is_interrupt": false

702}904}

703```905```

704 906 

705### Stop Input907| Field | Description |

908| :------------- | :------------------------------------------------------------------------------ |

909| `error` | String describing what went wrong |

910| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |

706 911 

707`stop_hook_active` is true when Claude Code is already continuing as a result of912#### PostToolUseFailure decision control

708a stop hook. Check this value or process the transcript to prevent Claude Code913 

709from running indefinitely.914`PostToolUseFailure` hooks can provide context to Claude after a tool failure. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:

915 

916| Field | Description |

917| :------------------ | :------------------------------------------------------------ |

918| `additionalContext` | Additional context for Claude to consider alongside the error |

710 919 

711```json theme={null}920```json theme={null}

712{921{

713 "session_id": "abc123",922 "hookSpecificOutput": {

714 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",923 "hookEventName": "PostToolUseFailure",

715 "cwd": "/Users/...",924 "additionalContext": "Additional information about the failure for Claude"

716 "permission_mode": "default",925 }

717 "hook_event_name": "Stop",

718 "stop_hook_active": true

719}926}

720```927```

721 928 

722### SubagentStop Input929### Notification

930 

931Runs when Claude Code sends notifications. Matches on notification type: `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`. Omit the matcher to run hooks for all notification types.

723 932 

724Triggered when a subagent finishes. The `transcript_path` is the main session's transcript, while `agent_transcript_path` is the subagent's own transcript stored in a nested `subagents/` folder.933Use separate matchers to run different handlers depending on the notification type. This configuration triggers a permission-specific alert script when Claude needs permission approval and a different notification when Claude has been idle:

725 934 

726```json theme={null}935```json theme={null}

727{936{

728 "session_id": "abc123",937 "hooks": {

729 "transcript_path": "~/.claude/projects/.../abc123.jsonl",938 "Notification": [

730 "cwd": "/Users/...",939 {

731 "permission_mode": "default",940 "matcher": "permission_prompt",

732 "hook_event_name": "SubagentStop",941 "hooks": [

733 "stop_hook_active": false,942 {

734 "agent_id": "def456",943 "type": "command",

735 "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl"944 "command": "/path/to/permission-alert.sh"

945 }

946 ]

947 },

948 {

949 "matcher": "idle_prompt",

950 "hooks": [

951 {

952 "type": "command",

953 "command": "/path/to/idle-notification.sh"

954 }

955 ]

956 }

957 ]

958 }

736}959}

737```960```

738 961 

739### PreCompact Input962#### Notification input

740 963 

741For `manual`, `custom_instructions` comes from what the user passes into964In addition to the [common input fields](#common-input-fields), Notification hooks receive `message` with the notification text, an optional `title`, and `notification_type` indicating which type fired.

742`/compact`. For `auto`, `custom_instructions` is empty.

743 965 

744```json theme={null}966```json theme={null}

745{967{

746 "session_id": "abc123",968 "session_id": "abc123",

747 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",969 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",

970 "cwd": "/Users/...",

748 "permission_mode": "default",971 "permission_mode": "default",

749 "hook_event_name": "PreCompact",972 "hook_event_name": "Notification",

750 "trigger": "manual",973 "message": "Claude needs your permission to use Bash",

751 "custom_instructions": ""974 "title": "Permission needed",

975 "notification_type": "permission_prompt"

752}976}

753```977```

754 978 

755### Setup Input979Notification hooks cannot block or modify notifications. In addition to the [JSON output fields](#json-output) available to all hooks, you can return `additionalContext` to add context to the conversation:

980 

981| Field | Description |

982| :------------------ | :------------------------------- |

983| `additionalContext` | String added to Claude's context |

984 

985### SubagentStart

986 

987Runs when a Claude Code subagent is spawned via the Task tool. Supports matchers to filter by agent type name (built-in agents like `Bash`, `Explore`, `Plan`, or custom agent names from `.claude/agents/`).

988 

989#### SubagentStart input

990 

991In addition to the [common input fields](#common-input-fields), SubagentStart hooks receive `agent_id` with the unique identifier for the subagent and `agent_type` with the agent name (built-in agents like `"Bash"`, `"Explore"`, `"Plan"`, or custom agent names).

756 992 

757```json theme={null}993```json theme={null}

758{994{

759 "session_id": "abc123",995 "session_id": "abc123",

760 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",996 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",

761 "cwd": "/Users/...",997 "cwd": "/Users/...",

762 "permission_mode": "default",998 "permission_mode": "default",

763 "hook_event_name": "Setup",999 "hook_event_name": "SubagentStart",

764 "trigger": "init"1000 "agent_id": "agent-abc123",

1001 "agent_type": "Explore"

765}1002}

766```1003```

767 1004 

768The `trigger` field will be either `"init"` (from `--init` or `--init-only`) or `"maintenance"` (from `--maintenance`).1005SubagentStart hooks cannot block subagent creation, but they can inject context into the subagent. In addition to the [JSON output fields](#json-output) available to all hooks, you can return:

769 1006 

770### SessionStart Input1007| Field | Description |

1008| :------------------ | :------------------------------------- |

1009| `additionalContext` | String added to the subagent's context |

771 1010 

772```json theme={null}1011```json theme={null}

773{1012{

774 "session_id": "abc123",1013 "hookSpecificOutput": {

775 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1014 "hookEventName": "SubagentStart",

776 "cwd": "/Users/...",1015 "additionalContext": "Follow security guidelines for this task"

777 "permission_mode": "default",1016 }

778 "hook_event_name": "SessionStart",

779 "source": "startup",

780 "model": "claude-sonnet-4-20250514"

781}1017}

782```1018```

783 1019 

784The `source` field indicates how the session started: `"startup"` for new sessions, `"resume"` for resumed sessions, `"clear"` after `/clear`, or `"compact"` after compaction. The `model` field contains the model identifier when available. If you start Claude Code with `claude --agent <name>`, an `agent_type` field contains the agent name.1020### SubagentStop

1021 

1022Runs when a Claude Code subagent has finished responding. Matches on agent type, same values as SubagentStart.

1023 

1024#### SubagentStop input

785 1025 

786### SubagentStart Input1026In addition to the [common input fields](#common-input-fields), SubagentStop hooks receive `stop_hook_active`, `agent_id`, `agent_type`, and `agent_transcript_path`. The `agent_type` field is the value used for matcher filtering. The `transcript_path` is the main session's transcript, while `agent_transcript_path` is the subagent's own transcript stored in a nested `subagents/` folder.

787 1027 

788```json theme={null}1028```json theme={null}

789{1029{

790 "session_id": "abc123",1030 "session_id": "abc123",

791 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1031 "transcript_path": "~/.claude/projects/.../abc123.jsonl",

792 "cwd": "/Users/...",1032 "cwd": "/Users/...",

793 "permission_mode": "default",1033 "permission_mode": "default",

794 "hook_event_name": "SubagentStart",1034 "hook_event_name": "SubagentStop",

795 "agent_id": "agent-abc123",1035 "stop_hook_active": false,

796 "agent_type": "Explore"1036 "agent_id": "def456",

1037 "agent_type": "Explore",

1038 "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl"

797}1039}

798```1040```

799 1041 

800Triggered when a subagent is spawned. The `agent_id` field contains the unique identifier for the subagent, and `agent_type` contains the agent name (built-in agents like `"Bash"`, `"Explore"`, `"Plan"`, or custom agent names).1042SubagentStop hooks use the same decision control format as [Stop hooks](#stop-decision-control).

1043 

1044### Stop

1045 

1046Runs when the main Claude Code agent has finished responding. Does not run if

1047the stoppage occurred due to a user interrupt.

801 1048 

802### SessionEnd Input1049#### Stop input

1050 

1051In addition to the [common input fields](#common-input-fields), Stop hooks receive `stop_hook_active`. This field is `true` when Claude Code is already continuing as a result of a stop hook. Check this value or process the transcript to prevent Claude Code from running indefinitely.

803 1052 

804```json theme={null}1053```json theme={null}

805{1054{


807 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1056 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",

808 "cwd": "/Users/...",1057 "cwd": "/Users/...",

809 "permission_mode": "default",1058 "permission_mode": "default",

810 "hook_event_name": "SessionEnd",1059 "hook_event_name": "Stop",

811 "reason": "exit"1060 "stop_hook_active": true

812}1061}

813```1062```

814 1063 

815## Hook Output1064#### Stop decision control

816 

817There are two mutually exclusive ways for hooks to return output back to Claude Code. The output

818communicates whether to block and any feedback that should be shown to Claude

819and the user.

820 1065 

821### Simple: Exit Code1066`Stop` and `SubagentStop` hooks can control whether Claude continues. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:

822 1067 

823Hooks communicate status through exit codes, stdout, and stderr:1068| Field | Description |

824 1069| :--------- | :------------------------------------------------------------------------- |

825* **Exit code 0**: Success. `stdout` is shown to the user in verbose mode1070| `decision` | `"block"` prevents Claude from stopping. Omit to allow Claude to stop |

826 (ctrl+o), except for `UserPromptSubmit` and `SessionStart`, where stdout is1071| `reason` | Required when `decision` is `"block"`. Tells Claude why it should continue |

827 added to the context. JSON output in `stdout` is parsed for structured control

828 (see [Advanced: JSON Output](#advanced-json-output)).

829* **Exit code 2**: Blocking error. Only `stderr` is used as the error message

830 and fed back to Claude. The format is `[command]: {stderr}`. JSON in `stdout`

831 is **not** processed for exit code 2. See per-hook-event behavior below.

832* **Other exit codes**: Non-blocking error. `stderr` is shown to the user in verbose mode (ctrl+o) with

833 format `Failed with non-blocking status code: {stderr}`. If `stderr` is empty,

834 it shows `No stderr output`. Execution continues.

835 

836<Warning>

837 Reminder: Claude Code does not see stdout if the exit code is 0, except for

838 the `UserPromptSubmit` hook where stdout is injected as context.

839</Warning>

840 

841#### Exit Code 2 Behavior

842 

843| Hook Event | Behavior |

844| ------------------- | ------------------------------------------------------------------ |

845| `PreToolUse` | Blocks the tool call, shows stderr to Claude |

846| `PermissionRequest` | Denies the permission, shows stderr to Claude |

847| `PostToolUse` | Shows stderr to Claude (tool already ran) |

848| `Notification` | N/A, shows stderr to user only |

849| `UserPromptSubmit` | Blocks prompt processing, erases prompt, shows stderr to user only |

850| `Stop` | Blocks stoppage, shows stderr to Claude |

851| `SubagentStop` | Blocks stoppage, shows stderr to Claude subagent |

852| `PreCompact` | N/A, shows stderr to user only |

853| `Setup` | N/A, shows stderr to user only |

854| `SessionStart` | N/A, shows stderr to user only |

855| `SessionEnd` | N/A, shows stderr to user only |

856 

857### Advanced: JSON Output

858 

859Hooks can return structured JSON in `stdout` for more sophisticated control.

860 

861<Warning>

862 JSON output is only processed when the hook exits with code 0. If your hook

863 exits with code 2 (blocking error), `stderr` text is used directly—any JSON in `stdout`

864 is ignored. For other non-zero exit codes, only `stderr` is shown to the user in verbose mode (ctrl+o).

865</Warning>

866 

867#### Common JSON Fields

868 

869All hook types can include these optional fields:

870 1072 

871```json theme={null}1073```json theme={null}

872{1074{

873 "continue": true, // Whether Claude should continue after hook execution (default: true)1075 "decision": "block",

874 "stopReason": "string", // Message shown when continue is false1076 "reason": "Must be provided when Claude is blocked from stopping"

875 

876 "suppressOutput": true, // Hide stdout from transcript mode (default: false)

877 "systemMessage": "string" // Optional warning message shown to the user

878}1077}

879```1078```

880 1079 

881If `continue` is false, Claude stops processing after the hooks run.1080### PreCompact

882 

883* For `PreToolUse`, this is different from `"permissionDecision": "deny"`, which

884 only blocks a specific tool call and provides automatic feedback to Claude.

885* For `PostToolUse`, this is different from `"decision": "block"`, which

886 provides automated feedback to Claude.

887* For `UserPromptSubmit`, this prevents the prompt from being processed.

888* For `Stop` and `SubagentStop`, this takes precedence over any

889 `"decision": "block"` output.

890* In all cases, `"continue" = false` takes precedence over any

891 `"decision": "block"` output.

892 

893`stopReason` accompanies `continue` with a reason shown to the user, not shown

894to Claude.

895 

896#### `PreToolUse` Decision Control

897 

898`PreToolUse` hooks can control whether a tool call proceeds.

899 1081 

900* `"allow"` bypasses the permission system. `permissionDecisionReason` is shown1082Runs before Claude Code is about to run a compact operation.

901 to the user but not to Claude.

902* `"deny"` prevents the tool call from executing. `permissionDecisionReason` is

903 shown to Claude.

904* `"ask"` asks the user to confirm the tool call in the UI.

905 `permissionDecisionReason` is shown to the user but not to Claude.

906 1083 

907Additionally, hooks can modify tool inputs before execution using `updatedInput`:1084The matcher value indicates whether compaction was triggered manually or automatically:

908 1085 

909* `updatedInput` modifies the tool's input parameters before the tool executes1086| Matcher | When it fires |

910* Combine with `"permissionDecision": "allow"` to modify the input and auto-approve the tool call1087| :------- | :------------------------------------------- |

911* Combine with `"permissionDecision": "ask"` to modify the input and show it to the user for confirmation1088| `manual` | `/compact` |

1089| `auto` | Auto-compact when the context window is full |

912 1090 

913Hooks can also provide context to Claude using `additionalContext`:1091#### PreCompact input

914 1092 

915* `"hookSpecificOutput.additionalContext"` adds a string to Claude's context before the tool executes.1093In addition to the [common input fields](#common-input-fields), PreCompact hooks receive `trigger` and `custom_instructions`. For `manual`, `custom_instructions` contains what the user passes into `/compact`. For `auto`, `custom_instructions` is empty.

916 1094 

917```json theme={null}1095```json theme={null}

918{1096{

919 "hookSpecificOutput": {1097 "session_id": "abc123",

920 "hookEventName": "PreToolUse",1098 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",

921 "permissionDecision": "allow",1099 "cwd": "/Users/...",

922 "permissionDecisionReason": "My reason here",1100 "permission_mode": "default",

923 "updatedInput": {1101 "hook_event_name": "PreCompact",

924 "field_to_modify": "new value"1102 "trigger": "manual",

925 },1103 "custom_instructions": ""

926 "additionalContext": "Current environment: production. Proceed with caution."

927 }

928}1104}

929```1105```

930 1106 

931<Note>1107### SessionEnd

932 The `decision` and `reason` fields are deprecated for PreToolUse hooks.

933 Use `hookSpecificOutput.permissionDecision` and

934 `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields

935 `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.

936</Note>

937 

938#### `PermissionRequest` Decision Control

939 

940`PermissionRequest` hooks can allow or deny permission requests shown to the user.

941 1108 

942* For `"behavior": "allow"` you can also optionally pass in an `"updatedInput"` that modifies the tool's input parameters before the tool executes.1109Runs when a Claude Code session ends. Useful for cleanup tasks, logging session

943* For `"behavior": "deny"` you can also optionally pass in a `"message"` string that tells the model why the permission was denied, and a boolean `"interrupt"` which will stop Claude.1110statistics, or saving session state. Supports matchers to filter by exit reason.

944 1111 

945```json theme={null}1112The `reason` field in the hook input indicates why the session ended:

946{

947 "hookSpecificOutput": {

948 "hookEventName": "PermissionRequest",

949 "decision": {

950 "behavior": "allow",

951 "updatedInput": {

952 "command": "npm run lint"

953 }

954 }

955 }

956}

957```

958 1113 

959#### `PostToolUse` Decision Control1114| Reason | Description |

1115| :---------------------------- | :----------------------------------------- |

1116| `clear` | Session cleared with `/clear` command |

1117| `logout` | User logged out |

1118| `prompt_input_exit` | User exited while prompt input was visible |

1119| `bypass_permissions_disabled` | Bypass permissions mode was disabled |

1120| `other` | Other exit reasons |

960 1121 

961`PostToolUse` hooks can provide feedback to Claude after tool execution.1122#### SessionEnd input

962 1123 

963* `"block"` automatically prompts Claude with `reason`.1124In addition to the [common input fields](#common-input-fields), SessionEnd hooks receive a `reason` field indicating why the session ended. See the [reason table](#sessionend) above for all values.

964* `undefined` does nothing. `reason` is ignored.

965* `"hookSpecificOutput.additionalContext"` adds context for Claude to consider.

966 1125 

967```json theme={null}1126```json theme={null}

968{1127{

969 "decision": "block" | undefined,1128 "session_id": "abc123",

970 "reason": "Explanation for decision",1129 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",

971 "hookSpecificOutput": {1130 "cwd": "/Users/...",

972 "hookEventName": "PostToolUse",1131 "permission_mode": "default",

973 "additionalContext": "Additional information for Claude"1132 "hook_event_name": "SessionEnd",

974 }1133 "reason": "other"

975}1134}

976```1135```

977 1136 

978#### `UserPromptSubmit` Decision Control1137SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.

979 1138 

980`UserPromptSubmit` hooks can control whether a user prompt is processed and add context.1139## Prompt-based hooks

981 1140 

982**Adding context (exit code 0):**1141In addition to Bash command hooks (`type: "command"`), Claude Code supports prompt-based hooks (`type: "prompt"`) that use an LLM to evaluate whether to allow or block an action. Prompt-based hooks work with the following events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `UserPromptSubmit`, `Stop`, and `SubagentStop`.

983There are two ways to add context to the conversation:

984 1142 

9851. **Plain text stdout** (simpler): Any non-JSON text written to stdout is added1143### How prompt-based hooks work

986 as context. This is the easiest way to inject information.

987 1144 

9882. **JSON with `additionalContext`** (structured): Use the JSON format below for1145Instead of executing a Bash command, prompt-based hooks:

989 more control. The `additionalContext` field is added as context.1146 

11471. Send the hook input and your prompt to a Claude model, Haiku by default

11482. The LLM responds with structured JSON containing a decision

11493. Claude Code processes the decision automatically

990 1150 

991Both methods work with exit code 0. Plain stdout is shown as hook output in1151### Prompt hook configuration

992the transcript; `additionalContext` is added more discretely.

993 1152 

994**Blocking prompts:**1153Set `type` to `"prompt"` and provide a `prompt` string instead of a `command`. Use the `$ARGUMENTS` placeholder to inject the hook's JSON input data into your prompt text. Claude Code sends the combined prompt and input to a fast Claude model, which returns a JSON decision.

995 1154 

996* `"decision": "block"` prevents the prompt from being processed. The submitted1155This `Stop` hook asks the LLM to evaluate whether all tasks are complete before allowing Claude to finish:

997 prompt is erased from context. `"reason"` is shown to the user but not added

998 to context.

999* `"decision": undefined` (or omitted) allows the prompt to proceed normally.

1000 1156 

1001```json theme={null}1157```json theme={null}

1002{1158{

1003 "decision": "block" | undefined,1159 "hooks": {

1004 "reason": "Explanation for decision",1160 "Stop": [

1005 "hookSpecificOutput": {1161 {

1006 "hookEventName": "UserPromptSubmit",1162 "hooks": [

1007 "additionalContext": "My additional context here"1163 {

1164 "type": "prompt",

1165 "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."

1166 }

1167 ]

1168 }

1169 ]

1008 }1170 }

1009}1171}

1010```1172```

1011 1173 

1012<Note>1174| Field | Required | Description |

1013 The JSON format isn't required for simple use cases. To add context, you can print plain text to stdout with exit code 0. Use JSON when you need to1175| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |

1014 block prompts or want more structured control.1176| `type` | yes | Must be `"prompt"` |

1015</Note>1177| `prompt` | yes | The prompt text to send to the LLM. Use `$ARGUMENTS` as a placeholder for the hook input JSON. If `$ARGUMENTS` is not present, input JSON is appended to the prompt |

1016 1178| `model` | no | Model to use for evaluation. Defaults to a fast model |

1017#### `Stop`/`SubagentStop` Decision Control1179| `timeout` | no | Timeout in seconds. Default: 30 |

1018 1180 

1019`Stop` and `SubagentStop` hooks can control whether Claude must continue.1181### Response schema

1020 1182 

1021* `"block"` prevents Claude from stopping. You must populate `reason` for Claude1183The LLM must respond with JSON containing:

1022 to know how to proceed.

1023* `undefined` allows Claude to stop. `reason` is ignored.

1024 1184 

1025```json theme={null}1185```json theme={null}

1026{1186{

1027 "decision": "block" | undefined,1187 "ok": true | false,

1028 "reason": "Must be provided when Claude is blocked from stopping"1188 "reason": "Explanation for the decision"

1029}1189}

1030```1190```

1031 1191 

1032#### `Setup` Decision Control1192| Field | Description |

1193| :------- | :--------------------------------------------------------- |

1194| `ok` | `true` allows the action, `false` prevents it |

1195| `reason` | Required when `ok` is `false`. Explanation shown to Claude |

1033 1196 

1034`Setup` hooks allow you to load context and configure the environment during repository initialization or maintenance.1197### Example: Multi-criteria Stop hook

1035 1198 

1036* `"hookSpecificOutput.additionalContext"` adds the string to the context.1199This `Stop` hook uses a detailed prompt to check three conditions before allowing Claude to stop. If `"ok"` is `false`, Claude continues working with the provided reason as its next instruction. `SubagentStop` hooks use the same format to evaluate whether a [subagent](/en/sub-agents) should stop:

1037* Multiple hooks' `additionalContext` values are concatenated.

1038* Setup hooks have access to `CLAUDE_ENV_FILE` for persisting environment variables.

1039 1200 

1040```json theme={null}1201```json theme={null}

1041{1202{

1042 "hookSpecificOutput": {1203 "hooks": {

1043 "hookEventName": "Setup",1204 "Stop": [

1044 "additionalContext": "Repository initialized with custom configuration"1205 {

1206 "hooks": [

1207 {

1208 "type": "prompt",

1209 "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",

1210 "timeout": 30

1211 }

1212 ]

1213 }

1214 ]

1045 }1215 }

1046}1216}

1047```1217```

1048 1218 

1049#### `SessionStart` Decision Control1219## Agent-based hooks

1050 1220 

1051`SessionStart` hooks allow you to load in context at the start of a session.1221Agent-based hooks (`type: "agent"`) are like prompt-based hooks but with multi-turn tool access. Instead of a single LLM call, an agent hook spawns a subagent that can read files, search code, and inspect the codebase to verify conditions. Agent hooks support the same events as prompt-based hooks.

1052 1222 

1053* `"hookSpecificOutput.additionalContext"` adds the string to the context.1223### How agent hooks work

1054* Multiple hooks' `additionalContext` values are concatenated.

1055 1224 

1056```json theme={null}1225When an agent hook fires:

1057{

1058 "hookSpecificOutput": {

1059 "hookEventName": "SessionStart",

1060 "additionalContext": "My additional context here"

1061 }

1062}

1063```

1064 1226 

1065#### `SessionEnd` Decision Control12271. Claude Code spawns a subagent with your prompt and the hook's JSON input

1066 12282. The subagent can use tools like Read, Grep, and Glob to investigate

1067`SessionEnd` hooks run when a session ends. They cannot block session termination12293. After up to 50 turns, the subagent returns a structured `{ "ok": true/false }` decision

1068but can perform cleanup tasks.12304. Claude Code processes the decision the same way as a prompt hook

1069 

1070#### Exit Code Example: Bash Command Validation

1071 

1072```python theme={null}

1073#!/usr/bin/env python3

1074import json

1075import re

1076import sys

1077 

1078# Define validation rules as a list of (regex pattern, message) tuples

1079VALIDATION_RULES = [

1080 (

1081 r"\bgrep\b(?!.*\|)",

1082 "Use 'rg' (ripgrep) instead of 'grep' for better performance and features",

1083 ),

1084 (

1085 r"\bfind\s+\S+\s+-name\b",

1086 "Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",

1087 ),

1088]

1089 

1090 

1091def validate_command(command: str) -> list[str]:

1092 issues = []

1093 for pattern, message in VALIDATION_RULES:

1094 if re.search(pattern, command):

1095 issues.append(message)

1096 return issues

1097 

1098 

1099try:

1100 input_data = json.load(sys.stdin)

1101except json.JSONDecodeError as e:

1102 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)

1103 sys.exit(1)

1104 

1105tool_name = input_data.get("tool_name", "")

1106tool_input = input_data.get("tool_input", {})

1107command = tool_input.get("command", "")

1108 

1109if tool_name != "Bash" or not command:

1110 sys.exit(1)

1111 

1112# Validate the command

1113issues = validate_command(command)

1114 

1115if issues:

1116 for message in issues:

1117 print(f"• {message}", file=sys.stderr)

1118 # Exit code 2 blocks tool call and shows stderr to Claude

1119 sys.exit(2)

1120```

1121 1231 

1122#### JSON Output Example: UserPromptSubmit to Add Context and Validation1232Agent hooks are useful when verification requires inspecting actual files or test output, not just evaluating the hook input data alone.

1123 1233 

1124<Note>1234### Agent hook configuration

1125 For `UserPromptSubmit` hooks, you can inject context using either method:

1126 1235 

1127 * **Plain text stdout** with exit code 0: Simplest approach, prints text1236Set `type` to `"agent"` and provide a `prompt` string. The configuration fields are the same as [prompt hooks](#prompt-hook-configuration), with a longer default timeout:

1128 * **JSON output** with exit code 0: Use `"decision": "block"` to reject prompts,

1129 or `additionalContext` for structured context injection

1130 1237 

1131 Remember: Exit code 2 only uses `stderr` for the error message. To block using1238| Field | Required | Description |

1132 JSON (with a custom reason), use `"decision": "block"` with exit code 0.1239| :-------- | :------- | :------------------------------------------------------------------------------------------ |

1133</Note>1240| `type` | yes | Must be `"agent"` |

1134 1241| `prompt` | yes | Prompt describing what to verify. Use `$ARGUMENTS` as a placeholder for the hook input JSON |

1135```python theme={null}1242| `model` | no | Model to use. Defaults to a fast model |

1136#!/usr/bin/env python31243| `timeout` | no | Timeout in seconds. Default: 60 |

1137import json

1138import sys

1139import re

1140import datetime

1141 

1142# Load input from stdin

1143try:

1144 input_data = json.load(sys.stdin)

1145except json.JSONDecodeError as e:

1146 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)

1147 sys.exit(1)

1148 

1149prompt = input_data.get("prompt", "")

1150 

1151# Check for sensitive patterns

1152sensitive_patterns = [

1153 (r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),

1154]

1155 

1156for pattern, message in sensitive_patterns:

1157 if re.search(pattern, prompt):

1158 # Use JSON output to block with a specific reason

1159 output = {

1160 "decision": "block",

1161 "reason": f"Security policy violation: {message}. Please rephrase your request without sensitive information."

1162 }

1163 print(json.dumps(output))

1164 sys.exit(0)

1165 1244 

1166# Add current time to context1245The response schema is the same as prompt hooks: `{ "ok": true }` to allow or `{ "ok": false, "reason": "..." }` to block.

1167context = f"Current time: {datetime.datetime.now()}"

1168print(context)

1169 1246 

1170"""1247This `Stop` hook verifies that all unit tests pass before allowing Claude to finish:

1171The following is also equivalent:

1172print(json.dumps({

1173 "hookSpecificOutput": {

1174 "hookEventName": "UserPromptSubmit",

1175 "additionalContext": context,

1176 },

1177}))

1178"""

1179 1248 

1180# Allow the prompt to proceed with the additional context1249```json theme={null}

1181sys.exit(0)1250{

1182```1251 "hooks": {

1183 1252 "Stop": [

1184#### JSON Output Example: PreToolUse with Approval1253 {

1185 1254 "hooks": [

1186```python theme={null}1255 {

1187#!/usr/bin/env python31256 "type": "agent",

1188import json1257 "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",

1189import sys1258 "timeout": 120

1190 

1191# Load input from stdin

1192try:

1193 input_data = json.load(sys.stdin)

1194except json.JSONDecodeError as e:

1195 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)

1196 sys.exit(1)

1197 

1198tool_name = input_data.get("tool_name", "")

1199tool_input = input_data.get("tool_input", {})

1200 

1201# Example: Auto-approve file reads for documentation files

1202if tool_name == "Read":

1203 file_path = tool_input.get("file_path", "")

1204 if file_path.endswith((".md", ".mdx", ".txt", ".json")):

1205 # Use JSON output to auto-approve the tool call

1206 output = {

1207 "decision": "approve",

1208 "reason": "Documentation file auto-approved",

1209 "suppressOutput": True # Don't show in verbose mode

1210 }1259 }

1211 print(json.dumps(output))1260 ]

1212 sys.exit(0)1261 }

1213 1262 ]

1214# For other cases, let the normal permission flow proceed1263 }

1215sys.exit(0)1264}

1216```1265```

1217 1266 

1218## Working with MCP Tools1267## Run hooks in the background

1219 

1220Claude Code hooks work seamlessly with

1221[Model Context Protocol (MCP) tools](/en/mcp). When MCP servers

1222provide tools, they appear with a special naming pattern that you can match in

1223your hooks.

1224 

1225### MCP Tool Naming

1226 1268 

1227MCP tools follow the pattern `mcp__<server>__<tool>`, for example:1269By default, hooks block Claude's execution until they complete. For long-running tasks like deployments, test suites, or external API calls, set `"async": true` to run the hook in the background while Claude continues working. Async hooks cannot block or control Claude's behavior: response fields like `decision`, `permissionDecision`, and `continue` have no effect, because the action they would have controlled has already completed.

1228 1270 

1229* `mcp__memory__create_entities` - Memory server's create entities tool1271### Configure an async hook

1230* `mcp__filesystem__read_file` - Filesystem server's read file tool

1231* `mcp__github__search_repositories` - GitHub server's search tool

1232 1272 

1233### Configuring Hooks for MCP Tools1273Add `"async": true` to a command hook's configuration to run it in the background without blocking Claude. This field is only available on `type: "command"` hooks.

1234 1274 

1235You can target specific MCP tools or entire MCP servers:1275This hook runs a test script after every `Write` tool call. Claude continues working immediately while `run-tests.sh` executes for up to 120 seconds. When the script finishes, its output is delivered on the next conversation turn:

1236 1276 

1237```json theme={null}1277```json theme={null}

1238{1278{

1239 "hooks": {1279 "hooks": {

1240 "PreToolUse": [1280 "PostToolUse": [

1241 {

1242 "matcher": "mcp__memory__.*",

1243 "hooks": [

1244 {

1245 "type": "command",

1246 "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"

1247 }

1248 ]

1249 },

1250 {1281 {

1251 "matcher": "mcp__.*__write.*",1282 "matcher": "Write",

1252 "hooks": [1283 "hooks": [

1253 {1284 {

1254 "type": "command",1285 "type": "command",

1255 "command": "/home/user/scripts/validate-mcp-write.py"1286 "command": "/path/to/run-tests.sh",

1287 "async": true,

1288 "timeout": 120

1256 }1289 }

1257 ]1290 ]

1258 }1291 }


1261}1294}

1262```1295```

1263 1296 

1264## Examples1297The `timeout` field sets the maximum time in seconds for the background process. If not specified, async hooks use the same 10-minute default as sync hooks.

1265 

1266<Tip>

1267 For practical examples including code formatting, notifications, and file protection, see [More Examples](/en/hooks-guide#more-examples) in the get started guide.

1268</Tip>

1269 

1270## Security Considerations

1271 1298 

1272### Disclaimer1299### How async hooks execute

1273 1300 

1274**USE AT YOUR OWN RISK**: Claude Code hooks execute arbitrary shell commands on1301When an async hook fires, Claude Code starts the hook process and immediately continues without waiting for it to finish. The hook receives the same JSON input via stdin as a synchronous hook.

1275your system automatically. By using hooks, you acknowledge that:

1276 1302 

1277* You are solely responsible for the commands you configure1303After the background process exits, if the hook produced a JSON response with a `systemMessage` or `additionalContext` field, that content is delivered to Claude as context on the next conversation turn.

1278* Hooks can modify, delete, or access any files your user account can access

1279* Malicious or poorly written hooks can cause data loss or system damage

1280* Anthropic provides no warranty and assumes no liability for any damages

1281 resulting from hook usage

1282* You should thoroughly test hooks in a safe environment before production use

1283 1304 

1284Always review and understand any hook commands before adding them to your1305### Example: run tests after file changes

1285configuration.

1286 1306 

1287### Security Best Practices1307This hook starts a test suite in the background whenever Claude writes a file, then reports the results back to Claude when the tests finish. Save this script to `.claude/hooks/run-tests-async.sh` in your project and make it executable with `chmod +x`:

1288 1308 

1289Here are some key practices for writing more secure hooks:1309```bash theme={null}

1310#!/bin/bash

1311# run-tests-async.sh

1290 1312 

12911. **Validate and sanitize inputs** - Never trust input data blindly1313# Read hook input from stdin

12922. **Always quote shell variables** - Use `"$VAR"` not `$VAR`1314INPUT=$(cat)

12933. **Block path traversal** - Check for `..` in file paths1315FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

12944. **Use absolute paths** - Specify full paths for scripts (use

1295 "\$CLAUDE\_PROJECT\_DIR" for the project path)

12965. **Skip sensitive files** - Avoid `.env`, `.git/`, keys, etc.

1297 1316 

1298### Configuration Safety1317# Only run tests for source files

1318if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then

1319 exit 0

1320fi

1299 1321 

1300Direct edits to hooks in settings files don't take effect immediately. Claude1322# Run tests and report results via systemMessage

1301Code:1323RESULT=$(npm test 2>&1)

1324EXIT_CODE=$?

1302 1325 

13031. Captures a snapshot of hooks at startup1326if [ $EXIT_CODE -eq 0 ]; then

13042. Uses this snapshot throughout the session1327 echo "{\"systemMessage\": \"Tests passed after editing $FILE_PATH\"}"

13053. Warns if hooks are modified externally1328else

13064. Requires review in `/hooks` menu for changes to apply1329 echo "{\"systemMessage\": \"Tests failed after editing $FILE_PATH: $RESULT\"}"

1330fi

1331```

1307 1332 

1308This prevents malicious hook modifications from affecting your current session.1333Then add this configuration to `.claude/settings.json` in your project root. The `async: true` flag lets Claude keep working while tests run:

1309 1334 

1310## Hook Execution Details1335```json theme={null}

1336{

1337 "hooks": {

1338 "PostToolUse": [

1339 {

1340 "matcher": "Write|Edit",

1341 "hooks": [

1342 {

1343 "type": "command",

1344 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests-async.sh",

1345 "async": true,

1346 "timeout": 300

1347 }

1348 ]

1349 }

1350 ]

1351 }

1352}

1353```

1311 1354 

1312* **Timeout**: 60-second execution limit by default, configurable per command.1355### Limitations

1313 * A timeout for an individual command does not affect the other commands.

1314* **Parallelization**: All matching hooks run in parallel

1315* **Deduplication**: Multiple identical hook commands are deduplicated automatically

1316* **Environment**: Runs in current directory with Claude Code's environment

1317 * The `CLAUDE_PROJECT_DIR` environment variable is available and contains the

1318 absolute path to the project root directory (where Claude Code was started)

1319 * The `CLAUDE_CODE_REMOTE` environment variable indicates whether the hook is running in a remote (web) environment (`"true"`) or local CLI environment (not set or empty). Use this to run different logic based on execution context.

1320* **Input**: JSON via stdin

1321* **Output**:

1322 * PreToolUse/PermissionRequest/PostToolUse/Stop/SubagentStop: Progress shown in verbose mode (ctrl+o)

1323 * Notification/SessionEnd: Logged to debug only (`--debug`)

1324 * UserPromptSubmit/SessionStart/Setup: stdout added as context for Claude

1325 1356 

1326## Debugging1357Async hooks have several constraints compared to synchronous hooks:

1327 1358 

1328### Basic Troubleshooting1359* Only `type: "command"` hooks support `async`. Prompt-based hooks cannot run asynchronously.

1360* Async hooks cannot block tool calls or return decisions. By the time the hook completes, the triggering action has already proceeded.

1361* Hook output is delivered on the next conversation turn. If the session is idle, the response waits until the next user interaction.

1362* Each execution creates a separate background process. There is no deduplication across multiple firings of the same async hook.

1329 1363 

1330If your hooks aren't working:1364## Security considerations

1331 1365 

13321. **Check configuration** - Run `/hooks` to see if your hook is registered1366### Disclaimer

13332. **Verify syntax** - Ensure your JSON settings are valid

13343. **Test commands** - Run hook commands manually first

13354. **Check permissions** - Make sure scripts are executable

13365. **Review logs** - Use `claude --debug` to see hook execution details

1337 1367 

1338Common issues:1368Hooks run with your system user's full permissions.

1339 1369 

1340* **Quotes not escaped** - Use `\"` inside JSON strings1370<Warning>

1341* **Wrong matcher** - Check tool names match exactly (case-sensitive)1371 Hooks execute shell commands with your full user permissions. They can modify, delete, or access any files your user account can access. Review and test all hook commands before adding them to your configuration.

1342* **Command not found** - Use full paths for scripts1372</Warning>

1343 1373 

1344### Advanced Debugging1374### Security best practices

1345 1375 

1346For complex hook issues:1376Keep these practices in mind when writing hooks:

1347 1377 

13481. **Inspect hook execution** - Use `claude --debug` to see detailed hook1378* **Validate and sanitize inputs**: never trust input data blindly

1349 execution1379* **Always quote shell variables**: use `"$VAR"` not `$VAR`

13502. **Validate JSON schemas** - Test hook input/output with external tools1380* **Block path traversal**: check for `..` in file paths

13513. **Check environment variables** - Verify Claude Code's environment is correct1381* **Use absolute paths**: specify full paths for scripts, using `"$CLAUDE_PROJECT_DIR"` for the project root

13524. **Test edge cases** - Try hooks with unusual file paths or inputs1382* **Skip sensitive files**: avoid `.env`, `.git/`, keys, etc.

13535. **Monitor system resources** - Check for resource exhaustion during hook

1354 execution

13556. **Use structured logging** - Implement logging in your hook scripts

1356 1383 

1357### Debug Output Example1384## Debug hooks

1358 1385 

1359Use `claude --debug` to see hook execution details:1386Run `claude --debug` to see hook execution details, including which hooks matched, their exit codes, and output. Toggle verbose mode with `Ctrl+O` to see hook progress in the transcript.

1360 1387 

1361```1388```

1362[DEBUG] Executing hooks for PostToolUse:Write1389[DEBUG] Executing hooks for PostToolUse:Write


1364[DEBUG] Found 1 hook matchers in settings1391[DEBUG] Found 1 hook matchers in settings

1365[DEBUG] Matched 1 hooks for query "Write"1392[DEBUG] Matched 1 hooks for query "Write"

1366[DEBUG] Found 1 hook commands to execute1393[DEBUG] Found 1 hook commands to execute

1367[DEBUG] Executing hook command: <Your command> with timeout 60000ms1394[DEBUG] Executing hook command: <Your command> with timeout 600000ms

1368[DEBUG] Hook command completed with status 0: <Your stdout>1395[DEBUG] Hook command completed with status 0: <Your stdout>

1369```1396```

1370 1397 

1371Progress messages appear in verbose mode (ctrl+o) showing:1398For troubleshooting common issues like hooks not firing, infinite Stop hook loops, or configuration errors, see [Limitations and troubleshooting](/en/hooks-guide#limitations-and-troubleshooting) in the guide.

1372 

1373* Which hook is running

1374* Command being executed

1375* Success/failure status

1376* Output or error messages

hooks-guide.md +496 −205

Details

2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt2> 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.3> Use this file to discover all available pages before exploring further.

4 4 

5# Get started with Claude Code hooks5# Automate workflows with hooks

6 6 

7> Learn how to customize and extend Claude Code's behavior by registering shell commands7> Run shell commands automatically when Claude Code edits files, finishes tasks, or needs input. Format code, send notifications, validate commands, and enforce project rules.

8 8 

9Claude Code hooks are user-defined shell commands that execute at various points9Hooks are user-defined shell commands that execute at specific points in Claude Code's lifecycle. They provide deterministic control over Claude Code's behavior, ensuring certain actions always happen rather than relying on the LLM to choose to run them. Use hooks to enforce project rules, automate repetitive tasks, and integrate Claude Code with your existing tools.

10in Claude Code's lifecycle. Hooks provide deterministic control over Claude10 

11Code's behavior, ensuring certain actions always happen rather than relying on11For decisions that require judgment rather than deterministic rules, you can also use [prompt-based hooks](#prompt-based-hooks) or [agent-based hooks](#agent-based-hooks) that use a Claude model to evaluate conditions.

12the LLM to choose to run them.12 

13For other ways to extend Claude Code, see [skills](/en/skills) for giving Claude additional instructions and executable commands, [subagents](/en/sub-agents) for running tasks in isolated contexts, and [plugins](/en/plugins) for packaging extensions to share across projects.

13 14 

14<Tip>15<Tip>

15 For reference documentation on hooks, see [Hooks reference](/en/hooks).16 This guide covers common use cases and how to get started. For full event schemas, JSON input/output formats, and advanced features like async hooks and MCP tool hooks, see the [Hooks reference](/en/hooks).

16</Tip>17</Tip>

17 18 

18Example use cases for hooks include:19## Set up your first hook

19 20 

20* **Notifications**: Customize how you get notified when Claude Code is awaiting21The fastest way to create a hook is through the `/hooks` interactive menu in Claude Code. This walkthrough creates a desktop notification hook, so you get alerted whenever Claude is waiting for your input instead of watching the terminal.

21 your input or permission to run something.

22* **Automatic formatting**: Run `prettier` on .ts files, `gofmt` on .go files,

23 etc. after every file edit.

24* **Logging**: Track and count all executed commands for compliance or

25 debugging.

26* **Feedback**: Provide automated feedback when Claude Code produces code that

27 does not follow your codebase conventions.

28* **Custom permissions**: Block modifications to production files or sensitive

29 directories.

30 22 

31By encoding these rules as hooks rather than prompting instructions, you turn23<Steps>

32suggestions into app-level code that executes every time it is expected to run.24 <Step title="Open the hooks menu">

25 Type `/hooks` in the Claude Code CLI. You'll see a list of all available hook events, plus an option to disable all hooks. Each event corresponds to a point in Claude's lifecycle where you can run custom code. Select `Notification` to create a hook that fires when Claude needs your attention.

26 </Step>

33 27 

34<Warning>28 <Step title="Configure the matcher">

35 You must consider the security implication of hooks as you add them, because hooks run automatically during the agent loop with your current environment's credentials.29 The menu shows a list of matchers, which filter when the hook fires. Set the matcher to `*` to fire on all notification types. You can narrow it later by changing the matcher to a specific value like `permission_prompt` or `idle_prompt`.

36 For example, malicious hooks code can exfiltrate your data. Always review your hooks implementation before registering them.30 </Step>

37 31 

38 For full security best practices, see [Security Considerations](/en/hooks#security-considerations) in the hooks reference documentation.32 <Step title="Add your command">

39</Warning>33 Select `+ Add new hook…`. The menu prompts you for a shell command to run when the event fires. Hooks run any shell command you provide, so you can use your platform's built-in notification tool. Copy the command for your OS:

40 34 

41## Hook Events Overview35 <Tabs>

36 <Tab title="macOS">

37 Uses [`osascript`](https://ss64.com/mac/osascript.html) to trigger a native macOS notification through AppleScript:

42 38 

43Claude Code provides several hook events that run at different points in the39 ```

44workflow:40 osascript -e 'display notification "Claude Code needs your attention" with title "Claude Code"'

41 ```

42 </Tab>

45 43 

46* **PreToolUse**: Runs before tool calls (can block them)44 <Tab title="Linux">

47* **PermissionRequest**: Runs when a permission dialog is shown (can allow or deny)45 Uses `notify-send`, which is pre-installed on most Linux desktops with a notification daemon:

48* **PostToolUse**: Runs after tool calls complete

49* **UserPromptSubmit**: Runs when the user submits a prompt, before Claude processes it

50* **Notification**: Runs when Claude Code sends notifications

51* **Stop**: Runs when Claude Code finishes responding

52* **SubagentStop**: Runs when subagent tasks complete

53* **PreCompact**: Runs before Claude Code is about to run a compact operation

54* **Setup**: Runs when Claude Code is invoked with `--init`, `--init-only`, or `--maintenance` flags

55* **SessionStart**: Runs when Claude Code starts a new session or resumes an existing session

56* **SessionEnd**: Runs when Claude Code session ends

57 46 

58Each event receives different data and can control Claude's behavior in47 ```

59different ways.48 notify-send 'Claude Code' 'Claude Code needs your attention'

49 ```

50 </Tab>

60 51 

61## Quickstart52 <Tab title="Windows (PowerShell)">

53 Uses PowerShell to show a native message box through .NET's Windows Forms:

62 54 

63In this quickstart, you'll add a hook that logs the shell commands that Claude55 ```

64Code runs.56 powershell.exe -Command "[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')"

57 ```

58 </Tab>

59 </Tabs>

60 </Step>

65 61 

66### Prerequisites62 <Step title="Choose a storage location">

63 The menu asks where to save the hook configuration. Select `User settings` to store it in `~/.claude/settings.json`, which applies the hook to all your projects. You could also choose `Project settings` to scope it to the current project. See [Configure hook location](#configure-hook-location) for all available scopes.

64 </Step>

67 65 

68Install `jq` for JSON processing in the command line.66 <Step title="Test the hook">

67 Press `Esc` to return to the CLI. Ask Claude to do something that requires permission, then switch away from the terminal. You should receive a desktop notification.

68 </Step>

69</Steps>

69 70 

70### Step 1: Open hooks configuration71## What you can automate

71 72 

72Run the `/hooks` command and select73Hooks let you run code at key points in Claude Code's lifecycle: format files after edits, block commands before they execute, send notifications when Claude needs input, inject context at session start, and more. For the full list of hook events, see the [Hooks reference](/en/hooks#hook-lifecycle).

73the `PreToolUse` hook event.

74 74 

75`PreToolUse` hooks run before tool calls and can block them while providing75Each example includes a ready-to-use configuration block that you add to a [settings file](#configure-hook-location). The most common patterns:

76Claude feedback on what to do differently.

77 76 

78### Step 2: Add a matcher77* [Get notified when Claude needs input](#get-notified-when-claude-needs-input)

78* [Auto-format code after edits](#auto-format-code-after-edits)

79* [Block edits to protected files](#block-edits-to-protected-files)

80* [Re-inject context after compaction](#re-inject-context-after-compaction)

79 81 

80Select `+ Add new matcher…` to run your hook only on Bash tool calls.82### Get notified when Claude needs input

81 83 

82Type `Bash` for the matcher.84Get a desktop notification whenever Claude finishes working and needs your input, so you can switch to other tasks without checking the terminal.

83 85 

84<Note>You can use `*` to match all tools.</Note>86This hook uses the `Notification` event, which fires when Claude is waiting for input or permission. Each tab below uses the platform's native notification command. Add this to `~/.claude/settings.json`, or use the [interactive walkthrough](#set-up-your-first-hook) above to configure it with `/hooks`:

85 87 

86### Step 3: Add the hook88<Tabs>

89 <Tab title="macOS">

90 ```json theme={null}

91 {

92 "hooks": {

93 "Notification": [

94 {

95 "matcher": "",

96 "hooks": [

97 {

98 "type": "command",

99 "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"

100 }

101 ]

102 }

103 ]

104 }

105 }

106 ```

107 </Tab>

87 108 

88Select `+ Add new hook…` and enter this command:109 <Tab title="Linux">

110 ```json theme={null}

111 {

112 "hooks": {

113 "Notification": [

114 {

115 "matcher": "",

116 "hooks": [

117 {

118 "type": "command",

119 "command": "notify-send 'Claude Code' 'Claude Code needs your attention'"

120 }

121 ]

122 }

123 ]

124 }

125 }

126 ```

127 </Tab>

89 128 

90```bash theme={null}129 <Tab title="Windows (PowerShell)">

91jq -r '"\(.tool_input.command) - \(.tool_input.description // "No description")"' >> ~/.claude/bash-command-log.txt130 ```json theme={null}

131 {

132 "hooks": {

133 "Notification": [

134 {

135 "matcher": "",

136 "hooks": [

137 {

138 "type": "command",

139 "command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')\""

140 }

141 ]

142 }

143 ]

144 }

145 }

146 ```

147 </Tab>

148</Tabs>

149 

150### Auto-format code after edits

151 

152Automatically run [Prettier](https://prettier.io/) on every file Claude edits, so formatting stays consistent without manual intervention.

153 

154This hook uses the `PostToolUse` event with an `Edit|Write` matcher, so it runs only after file-editing tools. The command extracts the edited file path with [`jq`](https://jqlang.github.io/jq/) and passes it to Prettier. Add this to `.claude/settings.json` in your project root:

155 

156```json theme={null}

157{

158 "hooks": {

159 "PostToolUse": [

160 {

161 "matcher": "Edit|Write",

162 "hooks": [

163 {

164 "type": "command",

165 "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"

166 }

167 ]

168 }

169 ]

170 }

171}

92```172```

93 173 

94### Step 4: Save your configuration174<Note>

175 The Bash examples on this page use `jq` for JSON parsing. Install it with `brew install jq` (macOS), `apt-get install jq` (Debian/Ubuntu), or see [`jq` downloads](https://jqlang.github.io/jq/download/).

176</Note>

177 

178### Block edits to protected files

179 

180Prevent Claude from modifying sensitive files like `.env`, `package-lock.json`, or anything in `.git/`. Claude receives feedback explaining why the edit was blocked, so it can adjust its approach.

181 

182This example uses a separate script file that the hook calls. The script checks the target file path against a list of protected patterns and exits with code 2 to block the edit.

95 183 

96For storage location, select `User settings` since you're logging to your home184<Steps>

97directory. This hook will then apply to all projects, not just your current185 <Step title="Create the hook script">

98project.186 Save this to `.claude/hooks/protect-files.sh`:

187 

188 ```bash theme={null}

189 #!/bin/bash

190 # protect-files.sh

191 

192 INPUT=$(cat)

193 FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

194 

195 PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")

196 

197 for pattern in "${PROTECTED_PATTERNS[@]}"; do

198 if [[ "$FILE_PATH" == *"$pattern"* ]]; then

199 echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2

200 exit 2

201 fi

202 done

203 

204 exit 0

205 ```

206 </Step>

207 

208 <Step title="Make the script executable (macOS/Linux)">

209 Hook scripts must be executable for Claude Code to run them:

210 

211 ```bash theme={null}

212 chmod +x .claude/hooks/protect-files.sh

213 ```

214 </Step>

215 

216 <Step title="Register the hook">

217 Add a `PreToolUse` hook to `.claude/settings.json` that runs the script before any `Edit` or `Write` tool call:

218 

219 ```json theme={null}

220 {

221 "hooks": {

222 "PreToolUse": [

223 {

224 "matcher": "Edit|Write",

225 "hooks": [

226 {

227 "type": "command",

228 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"

229 }

230 ]

231 }

232 ]

233 }

234 }

235 ```

236 </Step>

237</Steps>

99 238 

100Then press `Esc` until you return to the REPL. Your hook is now registered.239### Re-inject context after compaction

101 240 

102### Step 5: Verify your hook241When Claude's context window fills up, compaction summarizes the conversation to free space. This can lose important details. Use a `SessionStart` hook with a `compact` matcher to re-inject critical context after every compaction.

103 242 

104Run `/hooks` again or check `~/.claude/settings.json` to see your configuration:243Any text your command writes to stdout is added to Claude's context. This example reminds Claude of project conventions and recent work. Add this to `.claude/settings.json` in your project root:

105 244 

106```json theme={null}245```json theme={null}

107{246{

108 "hooks": {247 "hooks": {

109 "PreToolUse": [248 "SessionStart": [

110 {249 {

111 "matcher": "Bash",250 "matcher": "compact",

112 "hooks": [251 "hooks": [

113 {252 {

114 "type": "command",253 "type": "command",

115 "command": "jq -r '\"\\(.tool_input.command) - \\(.tool_input.description // \"No description\")\"' >> ~/.claude/bash-command-log.txt"254 "command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'"

116 }255 }

117 ]256 ]

118 }257 }


121}260}

122```261```

123 262 

124### Step 6: Test your hook263You can replace the `echo` with any command that produces dynamic output, like `git log --oneline -5` to show recent commits. For injecting context on every session start, consider using [CLAUDE.md](/en/memory) instead. For environment variables, see [`CLAUDE_ENV_FILE`](/en/hooks#persist-environment-variables) in the reference.

125 264 

126Ask Claude to run a simple command like `ls` and check your log file:265## How hooks work

127 266 

128```bash theme={null}267Hook events fire at specific lifecycle points in Claude Code. When an event fires, all matching hooks run in parallel, and identical hook commands are automatically deduplicated. The table below shows each event and when it triggers:

129cat ~/.claude/bash-command-log.txt

130```

131 268 

132You should see entries like:269| Event | When it fires |

270| :------------------- | :--------------------------------------------------- |

271| `SessionStart` | When a session begins or resumes |

272| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |

273| `PreToolUse` | Before a tool call executes. Can block it |

274| `PermissionRequest` | When a permission dialog appears |

275| `PostToolUse` | After a tool call succeeds |

276| `PostToolUseFailure` | After a tool call fails |

277| `Notification` | When Claude Code sends a notification |

278| `SubagentStart` | When a subagent is spawned |

279| `SubagentStop` | When a subagent finishes |

280| `Stop` | When Claude finishes responding |

281| `PreCompact` | Before context compaction |

282| `SessionEnd` | When a session terminates |

133 283 

284Each hook has a `type` that determines how it runs. Most hooks use `"type": "command"`, which runs a shell command. Two other options use a Claude model to make decisions: `"type": "prompt"` for single-turn evaluation and `"type": "agent"` for multi-turn verification with tool access. See [Prompt-based hooks](#prompt-based-hooks) and [Agent-based hooks](#agent-based-hooks) for details.

285 

286### Read input and return output

287 

288Hooks communicate with Claude Code through stdin, stdout, stderr, and exit codes. When an event fires, Claude Code passes event-specific data as JSON to your script's stdin. Your script reads that data, does its work, and tells Claude Code what to do next via the exit code.

289 

290#### Hook input

291 

292Every event includes common fields like `session_id` and `cwd`, but each event type adds different data. For example, when Claude runs a Bash command, a `PreToolUse` hook receives something like this on stdin:

293 

294```json theme={null}

295{

296 "session_id": "abc123", // unique ID for this session

297 "cwd": "/Users/sarah/myproject", // working directory when the event fired

298 "hook_event_name": "PreToolUse", // which event triggered this hook

299 "tool_name": "Bash", // the tool Claude is about to use

300 "tool_input": { // the arguments Claude passed to the tool

301 "command": "npm test" // for Bash, this is the shell command

302 }

303}

134```304```

135ls - Lists files and directories305 

306Your script can parse that JSON and act on any of those fields. `UserPromptSubmit` hooks get the `prompt` text instead, `SessionStart` hooks get the `source` (startup, resume, compact), and so on. See [Common input fields](/en/hooks#common-input-fields) in the reference for shared fields, and each event's section for event-specific schemas.

307 

308#### Hook output

309 

310Your script tells Claude Code what to do next by writing to stdout or stderr and exiting with a specific code. For example, a `PreToolUse` hook that wants to block a command:

311 

312```bash theme={null}

313#!/bin/bash

314INPUT=$(cat)

315COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

316 

317if echo "$COMMAND" | grep -q "drop table"; then

318 echo "Blocked: dropping tables is not allowed" >&2 # stderr becomes Claude's feedback

319 exit 2 # exit 2 = block the action

320fi

321 

322exit 0 # exit 0 = let it proceed

136```323```

137 324 

138## More Examples325The exit code determines what happens next:

326 

327* **Exit 0**: the action proceeds. For `UserPromptSubmit` and `SessionStart` hooks, anything you write to stdout is added to Claude's context.

328* **Exit 2**: the action is blocked. Write a reason to stderr, and Claude receives it as feedback so it can adjust.

329* **Any other exit code**: the action proceeds. Stderr is logged but not shown to Claude. Toggle verbose mode with `Ctrl+O` to see these messages in the transcript.

330 

331#### Structured JSON output

332 

333Exit codes give you two options: allow or block. For more control, exit 0 and print a JSON object to stdout instead.

139 334 

140<Note>335<Note>

141 For a complete example implementation, see the [bash command validator example](https://github.com/anthropics/claude-code/blob/main/examples/hooks/bash_command_validator_example.py) in our public codebase.336 Use exit 2 to block with a stderr message, or exit 0 with JSON for structured control. Don't mix them: Claude Code ignores JSON when you exit 2.

142</Note>337</Note>

143 338 

144### Code Formatting Hook339For example, a `PreToolUse` hook can deny a tool call and tell Claude why, or escalate it to the user for approval:

340 

341```json theme={null}

342{

343 "hookSpecificOutput": {

344 "hookEventName": "PreToolUse",

345 "permissionDecision": "deny",

346 "permissionDecisionReason": "Use rg instead of grep for better performance"

347 }

348}

349```

350 

351Claude Code reads `permissionDecision` and cancels the tool call, then feeds `permissionDecisionReason` back to Claude as feedback. The three options are:

352 

353* `"allow"`: proceed without showing a permission prompt

354* `"deny"`: cancel the tool call and send the reason to Claude

355* `"ask"`: show the permission prompt to the user as normal

145 356 

146Automatically format TypeScript files after editing:357For `UserPromptSubmit` hooks, use `additionalContext` instead to inject text into Claude's context. See [Control behavior with JSON output](/en/hooks#json-output) in the reference for the full JSON schema. Prompt-based hooks (`type: "prompt"`) handle output differently: see [Prompt-based hooks](#prompt-based-hooks).

358 

359### Filter hooks with matchers

360 

361Without a matcher, a hook fires on every occurrence of its event. Matchers let you narrow that down. For example, if you want to run a formatter only after file edits (not after every tool call), add a matcher to your `PostToolUse` hook:

147 362 

148```json theme={null}363```json theme={null}

149{364{


152 {367 {

153 "matcher": "Edit|Write",368 "matcher": "Edit|Write",

154 "hooks": [369 "hooks": [

155 {370 { "type": "command", "command": "prettier --write ..." }

156 "type": "command",

157 "command": "jq -r '.tool_input.file_path' | { read file_path; if echo \"$file_path\" | grep -q '\\.ts$'; then npx prettier --write \"$file_path\"; fi; }"

158 }

159 ]371 ]

160 }372 }

161 ]373 ]


163}375}

164```376```

165 377 

166### Markdown Formatting Hook378The `"Edit|Write"` matcher is a regex pattern that matches the tool name. The hook only fires when Claude uses the `Edit` or `Write` tool, not when it uses `Bash`, `Read`, or any other tool.

167 379 

168Automatically fix missing language tags and formatting issues in markdown files:380Each event type matches on a specific field. Matchers support exact strings and regex patterns:

169 381 

170```json theme={null}382| Event | What the matcher filters | Example matcher values |

171{383| :--------------------------------------------------------------------- | :------------------------ | :----------------------------------------------------------------------- |

384| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |

385| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |

386| `SessionEnd` | why the session ended | `clear`, `logout`, `prompt_input_exit`, `other` |

387| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |

388| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |

389| `PreCompact` | what triggered compaction | `manual`, `auto` |

390| `UserPromptSubmit`, `Stop` | no matcher support | always fires on every occurrence |

391| `SubagentStop` | agent type | same values as `SubagentStart` |

392 

393A few more examples showing matchers on different event types:

394 

395<Tabs>

396 <Tab title="Log every Bash command">

397 Match only `Bash` tool calls and log each command to a file. The `PostToolUse` event fires after the command completes, so `tool_input.command` contains what ran. The hook receives the event data as JSON on stdin, and `jq -r '.tool_input.command'` extracts just the command string, which `>>` appends to the log file:

398 

399 ```json theme={null}

400 {

172 "hooks": {401 "hooks": {

173 "PostToolUse": [402 "PostToolUse": [

174 {403 {

175 "matcher": "Edit|Write",404 "matcher": "Bash",

176 "hooks": [405 "hooks": [

177 {406 {

178 "type": "command",407 "type": "command",

179 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/markdown_formatter.py"408 "command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt"

180 }409 }

181 ]410 ]

182 }411 }

183 ]412 ]

184 }413 }

185}414 }

186```415 ```

416 </Tab>

187 417 

188Create `.claude/hooks/markdown_formatter.py` with this content:418 <Tab title="Match MCP tools">

189 419 MCP tools use a different naming convention than built-in tools: `mcp__<server>__<tool>`, where `<server>` is the MCP server name and `<tool>` is the tool it provides. For example, `mcp__github__search_repositories` or `mcp__filesystem__read_file`. Use a regex matcher to target all tools from a specific server, or match across servers with a pattern like `mcp__.*__write.*`. See [Match MCP tools](/en/hooks#match-mcp-tools) in the reference for the full list of examples.

190````python theme={null}

191#!/usr/bin/env python3

192"""

193Markdown formatter for Claude Code output.

194Fixes missing language tags and spacing issues while preserving code content.

195"""

196import json

197import sys

198import re

199import os

200 

201def detect_language(code):

202 """Best-effort language detection from code content."""

203 s = code.strip()

204

205 # JSON detection

206 if re.search(r'^\s*[{\[]', s):

207 try:

208 json.loads(s)

209 return 'json'

210 except:

211 pass

212

213 # Python detection

214 if re.search(r'^\s*def\s+\w+\s*\(', s, re.M) or \

215 re.search(r'^\s*(import|from)\s+\w+', s, re.M):

216 return 'python'

217

218 # JavaScript detection

219 if re.search(r'\b(function\s+\w+\s*\(|const\s+\w+\s*=)', s) or \

220 re.search(r'=>|console\.(log|error)', s):

221 return 'javascript'

222

223 # Bash detection

224 if re.search(r'^#!.*\b(bash|sh)\b', s, re.M) or \

225 re.search(r'\b(if|then|fi|for|in|do|done)\b', s):

226 return 'bash'

227

228 # SQL detection

229 if re.search(r'\b(SELECT|INSERT|UPDATE|DELETE|CREATE)\s+', s, re.I):

230 return 'sql'

231

232 return 'text'

233 

234def format_markdown(content):

235 """Format markdown content with language detection."""

236 # Fix unlabeled code fences

237 def add_lang_to_fence(match):

238 indent, info, body, closing = match.groups()

239 if not info.strip():

240 lang = detect_language(body)

241 return f"{indent}```{lang}\n{body}{closing}\n"

242 return match.group(0)

243

244 fence_pattern = r'(?ms)^([ \t]{0,3})```([^\n]*)\n(.*?)(\n\1```)\s*$'

245 content = re.sub(fence_pattern, add_lang_to_fence, content)

246

247 # Fix excessive blank lines (only outside code fences)

248 content = re.sub(r'\n{3,}', '\n\n', content)

249

250 return content.rstrip() + '\n'

251 

252# Main execution

253try:

254 input_data = json.load(sys.stdin)

255 file_path = input_data.get('tool_input', {}).get('file_path', '')

256

257 if not file_path.endswith(('.md', '.mdx')):

258 sys.exit(0) # Not a markdown file

259

260 if os.path.exists(file_path):

261 with open(file_path, 'r', encoding='utf-8') as f:

262 content = f.read()

263

264 formatted = format_markdown(content)

265

266 if formatted != content:

267 with open(file_path, 'w', encoding='utf-8') as f:

268 f.write(formatted)

269 print(f"✓ Fixed markdown formatting in {file_path}")

270

271except Exception as e:

272 print(f"Error formatting markdown: {e}", file=sys.stderr)

273 sys.exit(1)

274````

275 

276Make the script executable:

277 420 

278```bash theme={null}421 The command below extracts the tool name from the hook's JSON input with `jq` and writes it to stderr, where it shows up in verbose mode (`Ctrl+O`):

279chmod +x .claude/hooks/markdown_formatter.py

280```

281 422 

282This hook automatically:423 ```json theme={null}

424 {

425 "hooks": {

426 "PreToolUse": [

427 {

428 "matcher": "mcp__github__.*",

429 "hooks": [

430 {

431 "type": "command",

432 "command": "echo \"GitHub tool called: $(jq -r '.tool_name')\" >&2"

433 }

434 ]

435 }

436 ]

437 }

438 }

439 ```

440 </Tab>

283 441 

284* Detects programming languages in unlabeled code blocks442 <Tab title="Clean up on session end">

285* Adds appropriate language tags for syntax highlighting443 The `SessionEnd` event supports matchers on the reason the session ended. This hook only fires on `clear` (when you run `/clear`), not on normal exits:

286* Fixes excessive blank lines while preserving code content

287* Only processes markdown files (`.md`, `.mdx`)

288 444 

289### Custom Notification Hook445 ```json theme={null}

446 {

447 "hooks": {

448 "SessionEnd": [

449 {

450 "matcher": "clear",

451 "hooks": [

452 {

453 "type": "command",

454 "command": "rm -f /tmp/claude-scratch-*.txt"

455 }

456 ]

457 }

458 ]

459 }

460 }

461 ```

462 </Tab>

463</Tabs>

464 

465For full matcher syntax, see the [Hooks reference](/en/hooks#configuration).

466 

467### Configure hook location

468 

469Where you add a hook determines its scope:

470 

471| Location | Scope | Shareable |

472| :--------------------------------------------------------- | :--------------------------------- | :--------------------------------- |

473| `~/.claude/settings.json` | All your projects | No, local to your machine |

474| `.claude/settings.json` | Single project | Yes, can be committed to the repo |

475| `.claude/settings.local.json` | Single project | No, gitignored |

476| Managed policy settings | Organization-wide | Yes, admin-controlled |

477| [Plugin](/en/plugins) `hooks/hooks.json` | When plugin is enabled | Yes, bundled with the plugin |

478| [Skill](/en/skills) or [agent](/en/sub-agents) frontmatter | While the skill or agent is active | Yes, defined in the component file |

479 

480You can also use the [`/hooks` menu](/en/hooks#the-hooks-menu) in Claude Code to add, delete, and view hooks interactively. To disable all hooks at once, use the toggle at the bottom of the `/hooks` menu or set `"disableAllHooks": true` in your settings file.

290 481 

291Get desktop notifications when Claude needs input:482Hooks added through the `/hooks` menu take effect immediately. If you edit settings files directly while Claude Code is running, the changes won't take effect until you review them in the `/hooks` menu or restart your session.

483 

484## Prompt-based hooks

485 

486For decisions that require judgment rather than deterministic rules, use `type: "prompt"` hooks. Instead of running a shell command, Claude Code sends your prompt and the hook's input data to a Claude model (Haiku by default) to make the decision. You can specify a different model with the `model` field if you need more capability.

487 

488The model's only job is to return a yes/no decision as JSON:

489 

490* `"ok": true`: the action proceeds

491* `"ok": false`: the action is blocked. The model's `"reason"` is fed back to Claude so it can adjust.

492 

493This example uses a `Stop` hook to ask the model whether all requested tasks are complete. If the model returns `"ok": false`, Claude keeps working and uses the `reason` as its next instruction:

292 494 

293```json theme={null}495```json theme={null}

294{496{

295 "hooks": {497 "hooks": {

296 "Notification": [498 "Stop": [

297 {499 {

298 "matcher": "",

299 "hooks": [500 "hooks": [

300 {501 {

301 "type": "command",502 "type": "prompt",

302 "command": "notify-send 'Claude Code' 'Awaiting your input'"503 "prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."

303 }504 }

304 ]505 ]

305 }506 }


308}509}

309```510```

310 511 

311### File Protection Hook512For full configuration options, see [Prompt-based hooks](/en/hooks#prompt-based-hooks) in the reference.

513 

514## Agent-based hooks

515 

516When verification requires inspecting files or running commands, use `type: "agent"` hooks. Unlike prompt hooks which make a single LLM call, agent hooks spawn a subagent that can read files, search code, and use other tools to verify conditions before returning a decision.

517 

518Agent hooks use the same `"ok"` / `"reason"` response format as prompt hooks, but with a longer default timeout of 60 seconds and up to 50 tool-use turns.

312 519 

313Block edits to sensitive files:520This example verifies that tests pass before allowing Claude to stop:

314 521 

315```json theme={null}522```json theme={null}

316{523{

317 "hooks": {524 "hooks": {

318 "PreToolUse": [525 "Stop": [

319 {526 {

320 "matcher": "Edit|Write",

321 "hooks": [527 "hooks": [

322 {528 {

323 "type": "command",529 "type": "agent",

324 "command": "python3 -c \"import json, sys; data=json.load(sys.stdin); path=data.get('tool_input',{}).get('file_path',''); sys.exit(2 if any(p in path for p in ['.env', 'package-lock.json', '.git/']) else 0)\""530 "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",

531 "timeout": 120

325 }532 }

326 ]533 ]

327 }534 }


330}537}

331```538```

332 539 

540Use prompt hooks when the hook input data alone is enough to make a decision. Use agent hooks when you need to verify something against the actual state of the codebase.

541 

542For full configuration options, see [Agent-based hooks](/en/hooks#agent-based-hooks) in the reference.

543 

544## Limitations and troubleshooting

545 

546### Limitations

547 

548* Hooks communicate through stdout, stderr, and exit codes only. They cannot trigger slash commands or tool calls directly.

549* Hook timeout is 10 minutes by default, configurable per hook with the `timeout` field (in seconds).

550* `PostToolUse` hooks cannot undo actions since the tool has already executed.

551* `PermissionRequest` hooks do not fire in [non-interactive mode](/en/headless) (`-p`). Use `PreToolUse` hooks for automated permission decisions.

552* `Stop` hooks fire whenever Claude finishes responding, not only at task completion. They do not fire on user interrupts.

553 

554### Hook not firing

555 

556The hook is configured but never executes.

557 

558* Run `/hooks` and confirm the hook appears under the correct event

559* Check that the matcher pattern matches the tool name exactly (matchers are case-sensitive)

560* Verify you're triggering the right event type (e.g., `PreToolUse` fires before tool execution, `PostToolUse` fires after)

561* If using `PermissionRequest` hooks in non-interactive mode (`-p`), switch to `PreToolUse` instead

562 

563### Hook error in output

564 

565You see a message like "PreToolUse hook error: ..." in the transcript.

566 

567* Your script exited with a non-zero code unexpectedly. Test it manually by piping sample JSON:

568 ```bash theme={null}

569 echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh

570 echo $? # Check the exit code

571 ```

572* If you see "command not found", use absolute paths or `$CLAUDE_PROJECT_DIR` to reference scripts

573* If you see "jq: command not found", install `jq` or use Python/Node.js for JSON parsing

574* If the script isn't running at all, make it executable: `chmod +x ./my-hook.sh`

575 

576### `/hooks` shows no hooks configured

577 

578You edited a settings file but the hooks don't appear in the menu.

579 

580* Restart your session or open `/hooks` to reload. Hooks added through the `/hooks` menu take effect immediately, but manual file edits require a reload.

581* Verify your JSON is valid (trailing commas and comments are not allowed)

582* Confirm the settings file is in the correct location: `.claude/settings.json` for project hooks, `~/.claude/settings.json` for global hooks

583 

584### Stop hook runs forever

585 

586Claude keeps working in an infinite loop instead of stopping.

587 

588Your Stop hook script needs to check whether it already triggered a continuation. Parse the `stop_hook_active` field from the JSON input and exit early if it's `true`:

589 

590```bash theme={null}

591#!/bin/bash

592INPUT=$(cat)

593if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then

594 exit 0 # Allow Claude to stop

595fi

596# ... rest of your hook logic

597```

598 

599### JSON validation failed

600 

601Claude Code shows a JSON parsing error even though your hook script outputs valid JSON.

602 

603When Claude Code runs a hook, it spawns a shell that sources your profile (`~/.zshrc` or `~/.bashrc`). If your profile contains unconditional `echo` statements, that output gets prepended to your hook's JSON:

604 

605```

606Shell ready on arm64

607{"decision": "allow"}

608```

609 

610Claude Code tries to parse this as JSON and fails. To fix this, wrap echo statements in your shell profile so they only run in interactive shells:

611 

612```bash theme={null}

613# In ~/.zshrc or ~/.bashrc

614if [[ $- == *i* ]]; then

615 echo "Shell ready"

616fi

617```

618 

619The `$-` variable contains shell flags, and `i` means interactive. Hooks run in non-interactive shells, so the echo is skipped.

620 

621### Debug techniques

622 

623Toggle verbose mode with `Ctrl+O` to see hook output in the transcript, or run `claude --debug` for full execution details including which hooks matched and their exit codes.

624 

333## Learn more625## Learn more

334 626 

335* For reference documentation on hooks, see [Hooks reference](/en/hooks).627* [Hooks reference](/en/hooks): full event schemas, JSON output format, async hooks, and MCP tool hooks

336* For comprehensive security best practices and safety guidelines, see [Security Considerations](/en/hooks#security-considerations) in the hooks reference documentation.628* [Security considerations](/en/hooks#security-considerations): review before deploying hooks in shared or production environments

337* For troubleshooting steps and debugging techniques, see [Debugging](/en/hooks#debugging) in the hooks reference629* [Bash command validator example](https://github.com/anthropics/claude-code/blob/main/examples/hooks/bash_command_validator_example.py): complete reference implementation

338 documentation.

Details

288* Yellow: pending review288* Yellow: pending review

289* Red: changes requested289* Red: changes requested

290* Gray: draft290* Gray: draft

291* Purple: merged

291 292 

292`Cmd+click` (Mac) or `Ctrl+click` (Windows/Linux) the link to open the pull request in your browser. The status updates automatically every 60 seconds.293`Cmd+click` (Mac) or `Ctrl+click` (Windows/Linux) the link to open the pull request in your browser. The status updates automatically every 60 seconds.

293 294 

plugins.md +2 −2

Details

350 mkdir my-plugin/hooks350 mkdir my-plugin/hooks

351 ```351 ```

352 352 

353 Create `my-plugin/hooks/hooks.json` with your hooks configuration. Copy the `hooks` object from your `.claude/settings.json` or `settings.local.json`the format is the same:353 Create `my-plugin/hooks/hooks.json` with your hooks configuration. Copy the `hooks` object from your `.claude/settings.json` or `settings.local.json`, since the format is the same. The command receives hook input as JSON on stdin, so use `jq` to extract the file path:

354 354 

355 ```json my-plugin/hooks/hooks.json theme={null}355 ```json my-plugin/hooks/hooks.json theme={null}

356 {356 {


358 "PostToolUse": [358 "PostToolUse": [

359 {359 {

360 "matcher": "Write|Edit",360 "matcher": "Write|Edit",

361 "hooks": [{ "type": "command", "command": "npm run lint:fix $FILE" }]361 "hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npm run lint:fix" }]

362 }362 }

363 ]363 ]

364 }364 }

Details

119* `Stop`: When Claude attempts to stop119* `Stop`: When Claude attempts to stop

120* `SubagentStart`: When a subagent is started120* `SubagentStart`: When a subagent is started

121* `SubagentStop`: When a subagent attempts to stop121* `SubagentStop`: When a subagent attempts to stop

122* `Setup`: When `--init`, `--init-only`, or `--maintenance` flags are used

123* `SessionStart`: At the beginning of sessions122* `SessionStart`: At the beginning of sessions

124* `SessionEnd`: At the end of sessions123* `SessionEnd`: At the end of sessions

125* `PreCompact`: Before conversation history is compacted124* `PreCompact`: Before conversation history is compacted

settings.md +7 −4

Details

145| `attribution` | Customize attribution for git commits and pull requests. See [Attribution settings](#attribution-settings) | `{"commit": "🤖 Generated with Claude Code", "pr": ""}` |145| `attribution` | Customize attribution for git commits and pull requests. See [Attribution settings](#attribution-settings) | `{"commit": "🤖 Generated with Claude Code", "pr": ""}` |

146| `includeCoAuthoredBy` | **Deprecated**: Use `attribution` instead. Whether to include the `co-authored-by Claude` byline in git commits and pull requests (default: `true`) | `false` |146| `includeCoAuthoredBy` | **Deprecated**: Use `attribution` instead. Whether to include the `co-authored-by Claude` byline in git commits and pull requests (default: `true`) | `false` |

147| `permissions` | See table below for structure of permissions. | |147| `permissions` | See table below for structure of permissions. | |

148| `hooks` | Configure custom commands to run before or after tool executions. See [hooks documentation](/en/hooks) | `{"PreToolUse": {"Bash": "echo 'Running command...'"}}` |148| `hooks` | Configure custom commands to run at lifecycle events. See [hooks documentation](/en/hooks) for format | See [hooks](/en/hooks) |

149| `disableAllHooks` | Disable all [hooks](/en/hooks) | `true` |149| `disableAllHooks` | Disable all [hooks](/en/hooks) | `true` |

150| `allowManagedHooksOnly` | (Managed settings only) Prevent loading of user, project, and plugin hooks. Only allows managed hooks and SDK hooks. See [Hook configuration](#hook-configuration) | `true` |150| `allowManagedHooksOnly` | (Managed settings only) Prevent loading of user, project, and plugin hooks. Only allows managed hooks and SDK hooks. See [Hook configuration](#hook-configuration) | `true` |

151| `model` | Override the default model to use for Claude Code | `"claude-sonnet-4-5-20250929"` |151| `model` | Override the default model to use for Claude Code | `"claude-sonnet-4-5-20250929"` |


256**Filesystem and network restrictions** are configured via Read, Edit, and WebFetch permission rules, not via these sandbox settings.256**Filesystem and network restrictions** are configured via Read, Edit, and WebFetch permission rules, not via these sandbox settings.

257 257 

258| Keys | Description | Example |258| Keys | Description | Example |

259| :-------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------ |259| :---------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------ |

260| `enabled` | Enable bash sandboxing (macOS, Linux, and WSL2). Default: false | `true` |260| `enabled` | Enable bash sandboxing (macOS, Linux, and WSL2). Default: false | `true` |

261| `autoAllowBashIfSandboxed` | Auto-approve bash commands when sandboxed. Default: true | `true` |261| `autoAllowBashIfSandboxed` | Auto-approve bash commands when sandboxed. Default: true | `true` |

262| `excludedCommands` | Commands that should run outside of the sandbox | `["git", "docker"]` |262| `excludedCommands` | Commands that should run outside of the sandbox | `["git", "docker"]` |

263| `allowUnsandboxedCommands` | Allow commands to run outside the sandbox via the `dangerouslyDisableSandbox` parameter. When set to `false`, the `dangerouslyDisableSandbox` escape hatch is completely disabled and all commands must run sandboxed (or be in `excludedCommands`). Useful for enterprise policies that require strict sandboxing. Default: true | `false` |263| `allowUnsandboxedCommands` | Allow commands to run outside the sandbox via the `dangerouslyDisableSandbox` parameter. When set to `false`, the `dangerouslyDisableSandbox` escape hatch is completely disabled and all commands must run sandboxed (or be in `excludedCommands`). Useful for enterprise policies that require strict sandboxing. Default: true | `false` |

264| `network.allowUnixSockets` | Unix socket paths accessible in sandbox (for SSH agents, etc.) | `["~/.ssh/agent-socket"]` |264| `network.allowUnixSockets` | Unix socket paths accessible in sandbox (for SSH agents, etc.) | `["~/.ssh/agent-socket"]` |

265| `network.allowAllUnixSockets` | Allow all Unix socket connections in sandbox. Default: false | `true` |

265| `network.allowLocalBinding` | Allow binding to localhost ports (macOS only). Default: false | `true` |266| `network.allowLocalBinding` | Allow binding to localhost ports (macOS only). Default: false | `true` |

267| `network.allowedDomains` | Array of domains to allow for outbound network traffic. Supports wildcards (e.g., `*.example.com`). | `["github.com", "*.npmjs.org"]` |

266| `network.httpProxyPort` | HTTP proxy port used if you wish to bring your own proxy. If not specified, Claude will run its own proxy. | `8080` |268| `network.httpProxyPort` | HTTP proxy port used if you wish to bring your own proxy. If not specified, Claude will run its own proxy. | `8080` |

267| `network.socksProxyPort` | SOCKS5 proxy port used if you wish to bring your own proxy. If not specified, Claude will run its own proxy. | `8081` |269| `network.socksProxyPort` | SOCKS5 proxy port used if you wish to bring your own proxy. If not specified, Claude will run its own proxy. | `8081` |

268| `enableWeakerNestedSandbox` | Enable weaker sandbox for unprivileged Docker environments (Linux and WSL2 only). **Reduces security.** Default: false | `true` |270| `enableWeakerNestedSandbox` | Enable weaker sandbox for unprivileged Docker environments (Linux and WSL2 only). **Reduces security.** Default: false | `true` |


276 "autoAllowBashIfSandboxed": true,278 "autoAllowBashIfSandboxed": true,

277 "excludedCommands": ["docker"],279 "excludedCommands": ["docker"],

278 "network": {280 "network": {

281 "allowedDomains": ["github.com", "*.npmjs.org", "registry.yarnpkg.com"],

279 "allowUnixSockets": [282 "allowUnixSockets": [

280 "/var/run/docker.sock"283 "/var/run/docker.sock"

281 ],284 ],


785| :--------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |788| :--------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |

786| `ANTHROPIC_API_KEY` | API key sent as `X-Api-Key` header, typically for the Claude SDK (for interactive usage, run `/login`) | |789| `ANTHROPIC_API_KEY` | API key sent as `X-Api-Key` header, typically for the Claude SDK (for interactive usage, run `/login`) | |

787| `ANTHROPIC_AUTH_TOKEN` | Custom value for the `Authorization` header (the value you set here will be prefixed with `Bearer `) | |790| `ANTHROPIC_AUTH_TOKEN` | Custom value for the `Authorization` header (the value you set here will be prefixed with `Bearer `) | |

788| `ANTHROPIC_CUSTOM_HEADERS` | Custom headers you want to add to the request (in `Name: Value` format) | |791| `ANTHROPIC_CUSTOM_HEADERS` | Custom headers to add to requests (`Name: Value` format, newline-separated for multiple headers) | |

789| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | See [Model configuration](/en/model-config#environment-variables) | |792| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | See [Model configuration](/en/model-config#environment-variables) | |

790| `ANTHROPIC_DEFAULT_OPUS_MODEL` | See [Model configuration](/en/model-config#environment-variables) | |793| `ANTHROPIC_DEFAULT_OPUS_MODEL` | See [Model configuration](/en/model-config#environment-variables) | |

791| `ANTHROPIC_DEFAULT_SONNET_MODEL` | See [Model configuration](/en/model-config#environment-variables) | |794| `ANTHROPIC_DEFAULT_SONNET_MODEL` | See [Model configuration](/en/model-config#environment-variables) | |


950 953 

951The hook writes to `$CLAUDE_ENV_FILE`, which is then sourced before each Bash command. This is ideal for team-shared project configurations.954The hook writes to `$CLAUDE_ENV_FILE`, which is then sourced before each Bash command. This is ideal for team-shared project configurations.

952 955 

953See [SessionStart hooks](/en/hooks#persisting-environment-variables) for more details on Option 3.956See [SessionStart hooks](/en/hooks#persist-environment-variables) for more details on Option 3.

954 957 

955### Extending tools with hooks958### Extending tools with hooks

956 959 

setup.md +5 −5

Details

27 <Tab title="Native Install (Recommended)">27 <Tab title="Native Install (Recommended)">

28 **macOS, Linux, WSL:**28 **macOS, Linux, WSL:**

29 29 

30 ```bash theme={null} theme={null} theme={null} theme={null} theme={null}30 ```bash theme={null}

31 curl -fsSL https://claude.ai/install.sh | bash31 curl -fsSL https://claude.ai/install.sh | bash

32 ```32 ```

33 33 

34 **Windows PowerShell:**34 **Windows PowerShell:**

35 35 

36 ```powershell theme={null} theme={null} theme={null} theme={null} theme={null}36 ```powershell theme={null}

37 irm https://claude.ai/install.ps1 | iex37 irm https://claude.ai/install.ps1 | iex

38 ```38 ```

39 39 

40 **Windows CMD:**40 **Windows CMD:**

41 41 

42 ```batch theme={null} theme={null} theme={null} theme={null} theme={null}42 ```batch theme={null}

43 curl -fsSL https://claude.ai/install.cmd -o install.cmd && install.cmd && del install.cmd43 curl -fsSL https://claude.ai/install.cmd -o install.cmd && install.cmd && del install.cmd

44 ```44 ```

45 45 


49 </Tab>49 </Tab>

50 50 

51 <Tab title="Homebrew">51 <Tab title="Homebrew">

52 ```sh theme={null} theme={null} theme={null} theme={null} theme={null}52 ```sh theme={null}

53 brew install --cask claude-code53 brew install --cask claude-code

54 ```54 ```

55 55 


59 </Tab>59 </Tab>

60 60 

61 <Tab title="WinGet">61 <Tab title="WinGet">

62 ```powershell theme={null} theme={null} theme={null} theme={null} theme={null}62 ```powershell theme={null}

63 winget install Anthropic.ClaudeCode63 winget install Anthropic.ClaudeCode

64 ```64 ```

65 65 

skills.md +1 −1

Details

175| `model` | No | Model to use when this skill is active. |175| `model` | No | Model to use when this skill is active. |

176| `context` | No | Set to `fork` to run in a forked subagent context. |176| `context` | No | Set to `fork` to run in a forked subagent context. |

177| `agent` | No | Which subagent type to use when `context: fork` is set. |177| `agent` | No | Which subagent type to use when `context: fork` is set. |

178| `hooks` | No | Hooks scoped to this skill's lifecycle. See [Hooks](/en/hooks) for configuration format. |178| `hooks` | No | Hooks scoped to this skill's lifecycle. See [Hooks in skills and agents](/en/hooks#hooks-in-skills-and-agents) for configuration format. |

179 179 

180#### Available string substitutions180#### Available string substitutions

181 181 

sub-agents.md +11 −10

Details

298---298---

299```299```

300 300 

301Claude Code [passes hook input as JSON](/en/hooks#pretooluse-input) via stdin to hook commands. The validation script reads this JSON, extracts the Bash command, and [exits with code 2](/en/hooks#exit-code-2-behavior) to block write operations:301Claude Code [passes hook input as JSON](/en/hooks#pretooluse-input) via stdin to hook commands. The validation script reads this JSON, extracts the Bash command, and [exits with code 2](/en/hooks#exit-code-2-behavior-per-event) to block write operations:

302 302 

303```bash theme={null}303```bash theme={null}

304#!/bin/bash304#!/bin/bash


316exit 0316exit 0

317```317```

318 318 

319See [Hook input](/en/hooks#pretooluse-input) for the complete input schema and [exit codes](/en/hooks#simple-exit-code) for how exit codes affect behavior.319See [Hook input](/en/hooks#pretooluse-input) for the complete input schema and [exit codes](/en/hooks#exit-code-output) for how exit codes affect behavior.

320 320 

321#### Disable specific subagents321#### Disable specific subagents

322 322 


349 349 

350Define hooks directly in the subagent's markdown file. These hooks only run while that specific subagent is active and are cleaned up when it finishes.350Define hooks directly in the subagent's markdown file. These hooks only run while that specific subagent is active and are cleaned up when it finishes.

351 351 

352All [hook events](/en/hooks#hook-events) are supported. The most common events for subagents are:

353 

352| Event | Matcher input | When it fires |354| Event | Matcher input | When it fires |

353| :------------ | :------------ | :------------------------------ |355| :------------ | :------------ | :------------------------------------------------------------------ |

354| `PreToolUse` | Tool name | Before the subagent uses a tool |356| `PreToolUse` | Tool name | Before the subagent uses a tool |

355| `PostToolUse` | Tool name | After the subagent uses a tool |357| `PostToolUse` | Tool name | After the subagent uses a tool |

356| `Stop` | (none) | When the subagent finishes |358| `Stop` | (none) | When the subagent finishes (converted to `SubagentStop` at runtime) |

357 359 

358This example validates Bash commands with the `PreToolUse` hook and runs a linter after file edits with `PostToolUse`:360This example validates Bash commands with the `PreToolUse` hook and runs a linter after file edits with `PostToolUse`:

359 361 


379 381 

380#### Project-level hooks for subagent events382#### Project-level hooks for subagent events

381 383 

382Configure hooks in `settings.json` that respond to subagent lifecycle events in the main session. Use the `matcher` field to target specific agent types by name.384Configure hooks in `settings.json` that respond to subagent lifecycle events in the main session.

383 385 

384| Event | Matcher input | When it fires |386| Event | Matcher input | When it fires |

385| :-------------- | :-------------- | :------------------------------- |387| :-------------- | :-------------- | :------------------------------- |

386| `SubagentStart` | Agent type name | When a subagent begins execution |388| `SubagentStart` | Agent type name | When a subagent begins execution |

387| `SubagentStop` | Agent type name | When a subagent completes |389| `SubagentStop` | (none) | When any subagent completes |

388 390 

389This example runs setup and cleanup scripts only when the `db-agent` subagent starts and stops:391`SubagentStart` supports matchers to target specific agent types by name. `SubagentStop` fires for all subagent completions regardless of matcher values. This example runs a setup script only when the `db-agent` subagent starts, and a cleanup script when any subagent stops:

390 392 

391```json theme={null}393```json theme={null}

392{394{


401 ],403 ],

402 "SubagentStop": [404 "SubagentStop": [

403 {405 {

404 "matcher": "db-agent",

405 "hooks": [406 "hooks": [

406 { "type": "command", "command": "./scripts/cleanup-db-connection.sh" }407 { "type": "command", "command": "./scripts/cleanup-db-connection.sh" }

407 ]408 ]


691You cannot modify data. If asked to INSERT, UPDATE, DELETE, or modify schema, explain that you only have read access.692You cannot modify data. If asked to INSERT, UPDATE, DELETE, or modify schema, explain that you only have read access.

692```693```

693 694 

694Claude Code [passes hook input as JSON](/en/hooks#pretooluse-input) via stdin to hook commands. The validation script reads this JSON, extracts the command being executed, and checks it against a list of SQL write operations. If a write operation is detected, the script [exits with code 2](/en/hooks#exit-code-2-behavior) to block execution and returns an error message to Claude via stderr.695Claude Code [passes hook input as JSON](/en/hooks#pretooluse-input) via stdin to hook commands. The validation script reads this JSON, extracts the command being executed, and checks it against a list of SQL write operations. If a write operation is detected, the script [exits with code 2](/en/hooks#exit-code-2-behavior-per-event) to block execution and returns an error message to Claude via stderr.

695 696 

696Create the validation script anywhere in your project. The path must match the `command` field in your hook configuration:697Create the validation script anywhere in your project. The path must match the `command` field in your hook configuration:

697 698 


724chmod +x ./scripts/validate-readonly-query.sh725chmod +x ./scripts/validate-readonly-query.sh

725```726```

726 727 

727The hook receives JSON via stdin with the Bash command in `tool_input.command`. Exit code 2 blocks the operation and feeds the error message back to Claude. See [Hooks](/en/hooks#simple-exit-code) for details on exit codes and [Hook input](/en/hooks#pretooluse-input) for the complete input schema.728The hook receives JSON via stdin with the Bash command in `tool_input.command`. Exit code 2 blocks the operation and feeds the error message back to Claude. See [Hooks](/en/hooks#exit-code-output) for details on exit codes and [Hook input](/en/hooks#pretooluse-input) for the complete input schema.

728 729 

729## Next steps730## Next steps

730 731 

Details

383 383 

3841. **Ask Claude to add language tags**: Request "Add appropriate language tags to all code blocks in this markdown file."3841. **Ask Claude to add language tags**: Request "Add appropriate language tags to all code blocks in this markdown file."

385 385 

3862. **Use post-processing hooks**: Set up automatic formatting hooks to detect and add missing language tags. See the [markdown formatting hook example](/en/hooks-guide#markdown-formatting-hook) for implementation details.3862. **Use post-processing hooks**: Set up automatic formatting hooks to detect and add missing language tags. See [Auto-format code after edits](/en/hooks-guide#auto-format-code-after-edits) for an example of a PostToolUse formatting hook.

387 387 

3883. **Manual verification**: After generating markdown files, review them for proper code block formatting and request corrections if needed.3883. **Manual verification**: After generating markdown files, review them for proper code block formatting and request corrections if needed.

389 389