caruso 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1137 @@
1
+ # Hooks reference
2
+
3
+ > This page provides reference documentation for implementing hooks in Claude Code.
4
+
5
+ <Tip>
6
+ For a quickstart guide with examples, see [Get started with Claude Code hooks](/en/hooks-guide).
7
+ </Tip>
8
+
9
+ ## Configuration
10
+
11
+ Claude Code hooks are configured in your [settings files](/en/settings):
12
+
13
+ * `~/.claude/settings.json` - User settings
14
+ * `.claude/settings.json` - Project settings
15
+ * `.claude/settings.local.json` - Local project settings (not committed)
16
+ * Enterprise managed policy settings
17
+
18
+ <Note>
19
+ Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. See [Hook configuration](/en/settings#hook-configuration).
20
+ </Note>
21
+
22
+ ### Structure
23
+
24
+ Hooks are organized by matchers, where each matcher can have multiple hooks:
25
+
26
+ ```json theme={null}
27
+ {
28
+ "hooks": {
29
+ "EventName": [
30
+ {
31
+ "matcher": "ToolPattern",
32
+ "hooks": [
33
+ {
34
+ "type": "command",
35
+ "command": "your-command-here"
36
+ }
37
+ ]
38
+ }
39
+ ]
40
+ }
41
+ }
42
+ ```
43
+
44
+ * **matcher**: Pattern to match tool names, case-sensitive (only applicable for
45
+ `PreToolUse`, `PermissionRequest`, and `PostToolUse`)
46
+ * Simple strings match exactly: `Write` matches only the Write tool
47
+ * Supports regex: `Edit|Write` or `Notebook.*`
48
+ * Use `*` to match all tools. You can also use empty string (`""`) or leave
49
+ `matcher` blank.
50
+ * **hooks**: Array of hooks to execute when the pattern matches
51
+ * `type`: Hook execution type - `"command"` for bash commands or `"prompt"` for LLM-based evaluation
52
+ * `command`: (For `type: "command"`) The bash command to execute (can use `$CLAUDE_PROJECT_DIR` environment variable)
53
+ * `prompt`: (For `type: "prompt"`) The prompt to send to the LLM for evaluation
54
+ * `timeout`: (Optional) How long a hook should run, in seconds, before canceling that specific hook
55
+
56
+ For events like `UserPromptSubmit`, `Stop`, and `SubagentStop`
57
+ that don't use matchers, you can omit the matcher field:
58
+
59
+ ```json theme={null}
60
+ {
61
+ "hooks": {
62
+ "UserPromptSubmit": [
63
+ {
64
+ "hooks": [
65
+ {
66
+ "type": "command",
67
+ "command": "/path/to/prompt-validator.py"
68
+ }
69
+ ]
70
+ }
71
+ ]
72
+ }
73
+ }
74
+ ```
75
+
76
+ ### Project-Specific Hook Scripts
77
+
78
+ You can use the environment variable `CLAUDE_PROJECT_DIR` (only available when
79
+ Claude Code spawns the hook command) to reference scripts stored in your project,
80
+ ensuring they work regardless of Claude's current directory:
81
+
82
+ ```json theme={null}
83
+ {
84
+ "hooks": {
85
+ "PostToolUse": [
86
+ {
87
+ "matcher": "Write|Edit",
88
+ "hooks": [
89
+ {
90
+ "type": "command",
91
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
92
+ }
93
+ ]
94
+ }
95
+ ]
96
+ }
97
+ }
98
+ ```
99
+
100
+ ### Plugin hooks
101
+
102
+ [Plugins](/en/plugins) can provide hooks that integrate seamlessly with your user and project hooks. Plugin hooks are automatically merged with your configuration when plugins are enabled.
103
+
104
+ **How plugin hooks work**:
105
+
106
+ * Plugin hooks are defined in the plugin's `hooks/hooks.json` file or in a file given by a custom path to the `hooks` field.
107
+ * When a plugin is enabled, its hooks are merged with user and project hooks
108
+ * Multiple hooks from different sources can respond to the same event
109
+ * Plugin hooks use the `${CLAUDE_PLUGIN_ROOT}` environment variable to reference plugin files
110
+
111
+ **Example plugin hook configuration**:
112
+
113
+ ```json theme={null}
114
+ {
115
+ "description": "Automatic code formatting",
116
+ "hooks": {
117
+ "PostToolUse": [
118
+ {
119
+ "matcher": "Write|Edit",
120
+ "hooks": [
121
+ {
122
+ "type": "command",
123
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
124
+ "timeout": 30
125
+ }
126
+ ]
127
+ }
128
+ ]
129
+ }
130
+ }
131
+ ```
132
+
133
+ <Note>
134
+ Plugin hooks use the same format as regular hooks with an optional `description` field to explain the hook's purpose.
135
+ </Note>
136
+
137
+ <Note>
138
+ Plugin hooks run alongside your custom hooks. If multiple hooks match an event, they all execute in parallel.
139
+ </Note>
140
+
141
+ **Environment variables for plugins**:
142
+
143
+ * `${CLAUDE_PLUGIN_ROOT}`: Absolute path to the plugin directory
144
+ * `${CLAUDE_PROJECT_DIR}`: Project root directory (same as for project hooks)
145
+ * All standard environment variables are available
146
+
147
+ See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.
148
+
149
+ ## Prompt-Based Hooks
150
+
151
+ In 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.
152
+
153
+ ### How prompt-based hooks work
154
+
155
+ Instead of executing a bash command, prompt-based hooks:
156
+
157
+ 1. Send the hook input and your prompt to a fast LLM (Haiku)
158
+ 2. The LLM responds with structured JSON containing a decision
159
+ 3. Claude Code processes the decision automatically
160
+
161
+ ### Configuration
162
+
163
+ ```json theme={null}
164
+ {
165
+ "hooks": {
166
+ "Stop": [
167
+ {
168
+ "hooks": [
169
+ {
170
+ "type": "prompt",
171
+ "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
172
+ }
173
+ ]
174
+ }
175
+ ]
176
+ }
177
+ }
178
+ ```
179
+
180
+ **Fields:**
181
+
182
+ * `type`: Must be `"prompt"`
183
+ * `prompt`: The prompt text to send to the LLM
184
+ * Use `$ARGUMENTS` as a placeholder for the hook input JSON
185
+ * If `$ARGUMENTS` is not present, input JSON is appended to the prompt
186
+ * `timeout`: (Optional) Timeout in seconds (default: 30 seconds)
187
+
188
+ ### Response schema
189
+
190
+ The LLM must respond with JSON containing:
191
+
192
+ ```json theme={null}
193
+ {
194
+ "decision": "approve" | "block",
195
+ "reason": "Explanation for the decision",
196
+ "continue": false, // Optional: stops Claude entirely
197
+ "stopReason": "Message shown to user", // Optional: custom stop message
198
+ "systemMessage": "Warning or context" // Optional: shown to user
199
+ }
200
+ ```
201
+
202
+ **Response fields:**
203
+
204
+ * `decision`: `"approve"` allows the action, `"block"` prevents it
205
+ * `reason`: Explanation shown to Claude when decision is `"block"`
206
+ * `continue`: (Optional) If `false`, stops Claude's execution entirely
207
+ * `stopReason`: (Optional) Message shown when `continue` is false
208
+ * `systemMessage`: (Optional) Additional message shown to the user
209
+
210
+ ### Supported hook events
211
+
212
+ Prompt-based hooks work with any hook event, but are most useful for:
213
+
214
+ * **Stop**: Intelligently decide if Claude should continue working
215
+ * **SubagentStop**: Evaluate if a subagent has completed its task
216
+ * **UserPromptSubmit**: Validate user prompts with LLM assistance
217
+ * **PreToolUse**: Make context-aware permission decisions
218
+ * **PermissionRequest**: Intelligently allow or deny permission dialogs
219
+
220
+ ### Example: Intelligent Stop hook
221
+
222
+ ```json theme={null}
223
+ {
224
+ "hooks": {
225
+ "Stop": [
226
+ {
227
+ "hooks": [
228
+ {
229
+ "type": "prompt",
230
+ "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"decision\": \"approve\" or \"block\", \"reason\": \"your explanation\"}",
231
+ "timeout": 30
232
+ }
233
+ ]
234
+ }
235
+ ]
236
+ }
237
+ }
238
+ ```
239
+
240
+ ### Example: SubagentStop with custom logic
241
+
242
+ ```json theme={null}
243
+ {
244
+ "hooks": {
245
+ "SubagentStop": [
246
+ {
247
+ "hooks": [
248
+ {
249
+ "type": "prompt",
250
+ "prompt": "Evaluate if this subagent should stop. Input: $ARGUMENTS\n\nCheck if:\n- The subagent completed its assigned task\n- Any errors occurred that need fixing\n- Additional context gathering is needed\n\nReturn: {\"decision\": \"approve\" or \"block\", \"reason\": \"explanation\"}"
251
+ }
252
+ ]
253
+ }
254
+ ]
255
+ }
256
+ }
257
+ ```
258
+
259
+ ### Comparison with bash command hooks
260
+
261
+ | Feature | Bash Command Hooks | Prompt-Based Hooks |
262
+ | --------------------- | ----------------------- | ------------------------------ |
263
+ | **Execution** | Runs bash script | Queries LLM |
264
+ | **Decision logic** | You implement in code | LLM evaluates context |
265
+ | **Setup complexity** | Requires script file | Configure prompt |
266
+ | **Context awareness** | Limited to script logic | Natural language understanding |
267
+ | **Performance** | Fast (local execution) | Slower (API call) |
268
+ | **Use case** | Deterministic rules | Context-aware decisions |
269
+
270
+ ### Best practices
271
+
272
+ * **Be specific in prompts**: Clearly state what you want the LLM to evaluate
273
+ * **Include decision criteria**: List the factors the LLM should consider
274
+ * **Test your prompts**: Verify the LLM makes correct decisions for your use cases
275
+ * **Set appropriate timeouts**: Default is 30 seconds, adjust if needed
276
+ * **Use for complex decisions**: Bash hooks are better for simple, deterministic rules
277
+
278
+ See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.
279
+
280
+ ## Hook Events
281
+
282
+ ### PreToolUse
283
+
284
+ Runs after Claude creates tool parameters and before processing the tool call.
285
+
286
+ **Common matchers:**
287
+
288
+ * `Task` - Subagent tasks (see [subagents documentation](/en/sub-agents))
289
+ * `Bash` - Shell commands
290
+ * `Glob` - File pattern matching
291
+ * `Grep` - Content search
292
+ * `Read` - File reading
293
+ * `Edit` - File editing
294
+ * `Write` - File writing
295
+ * `WebFetch`, `WebSearch` - Web operations
296
+
297
+ Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.
298
+
299
+ ### PermissionRequest
300
+
301
+ Runs when the user is shown a permission dialog.
302
+ Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.
303
+
304
+ Recognizes the same matcher values as PreToolUse.
305
+
306
+ ### PostToolUse
307
+
308
+ Runs immediately after a tool completes successfully.
309
+
310
+ Recognizes the same matcher values as PreToolUse.
311
+
312
+ ### Notification
313
+
314
+ Runs when Claude Code sends notifications. Supports matchers to filter by notification type.
315
+
316
+ **Common matchers:**
317
+
318
+ * `permission_prompt` - Permission requests from Claude Code
319
+ * `idle_prompt` - When Claude is waiting for user input (after 60+ seconds of idle time)
320
+ * `auth_success` - Authentication success notifications
321
+ * `elicitation_dialog` - When Claude Code needs input for MCP tool elicitation
322
+
323
+ You can use matchers to run different hooks for different notification types, or omit the matcher to run hooks for all notifications.
324
+
325
+ **Example: Different notifications for different types**
326
+
327
+ ```json theme={null}
328
+ {
329
+ "hooks": {
330
+ "Notification": [
331
+ {
332
+ "matcher": "permission_prompt",
333
+ "hooks": [
334
+ {
335
+ "type": "command",
336
+ "command": "/path/to/permission-alert.sh"
337
+ }
338
+ ]
339
+ },
340
+ {
341
+ "matcher": "idle_prompt",
342
+ "hooks": [
343
+ {
344
+ "type": "command",
345
+ "command": "/path/to/idle-notification.sh"
346
+ }
347
+ ]
348
+ }
349
+ ]
350
+ }
351
+ }
352
+ ```
353
+
354
+ ### UserPromptSubmit
355
+
356
+ Runs when the user submits a prompt, before Claude processes it. This allows you
357
+ to add additional context based on the prompt/conversation, validate prompts, or
358
+ block certain types of prompts.
359
+
360
+ ### Stop
361
+
362
+ Runs when the main Claude Code agent has finished responding. Does not run if
363
+ the stoppage occurred due to a user interrupt.
364
+
365
+ ### SubagentStop
366
+
367
+ Runs when a Claude Code subagent (Task tool call) has finished responding.
368
+
369
+ ### PreCompact
370
+
371
+ Runs before Claude Code is about to run a compact operation.
372
+
373
+ **Matchers:**
374
+
375
+ * `manual` - Invoked from `/compact`
376
+ * `auto` - Invoked from auto-compact (due to full context window)
377
+
378
+ ### SessionStart
379
+
380
+ Runs when Claude Code starts a new session or resumes an existing session (which
381
+ currently does start a new session under the hood). Useful for loading in
382
+ development context like existing issues or recent changes to your codebase, installing dependencies, or setting up environment variables.
383
+
384
+ **Matchers:**
385
+
386
+ * `startup` - Invoked from startup
387
+ * `resume` - Invoked from `--resume`, `--continue`, or `/resume`
388
+ * `clear` - Invoked from `/clear`
389
+ * `compact` - Invoked from auto or manual compact.
390
+
391
+ #### Persisting environment variables
392
+
393
+ SessionStart 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.
394
+
395
+ **Example: Setting individual environment variables**
396
+
397
+ ```bash theme={null}
398
+ #!/bin/bash
399
+
400
+ if [ -n "$CLAUDE_ENV_FILE" ]; then
401
+ echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
402
+ echo 'export API_KEY=your-api-key' >> "$CLAUDE_ENV_FILE"
403
+ echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
404
+ fi
405
+
406
+ exit 0
407
+ ```
408
+
409
+ **Example: Persisting all environment changes from the hook**
410
+
411
+ When your setup modifies the environment (for example, `nvm use`), capture and persist all changes by diffing the environment:
412
+
413
+ ```bash theme={null}
414
+ #!/bin/bash
415
+
416
+ ENV_BEFORE=$(export -p | sort)
417
+
418
+ # Run your setup commands that modify the environment
419
+ source ~/.nvm/nvm.sh
420
+ nvm use 20
421
+
422
+ if [ -n "$CLAUDE_ENV_FILE" ]; then
423
+ ENV_AFTER=$(export -p | sort)
424
+ comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
425
+ fi
426
+
427
+ exit 0
428
+ ```
429
+
430
+ Any variables written to this file will be available in all subsequent bash commands that Claude Code executes during the session.
431
+
432
+ <Note>
433
+ `CLAUDE_ENV_FILE` is only available for SessionStart hooks. Other hook types do not have access to this variable.
434
+ </Note>
435
+
436
+ ### SessionEnd
437
+
438
+ Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
439
+ statistics, or saving session state.
440
+
441
+ The `reason` field in the hook input will be one of:
442
+
443
+ * `clear` - Session cleared with /clear command
444
+ * `logout` - User logged out
445
+ * `prompt_input_exit` - User exited while prompt input was visible
446
+ * `other` - Other exit reasons
447
+
448
+ ## Hook Input
449
+
450
+ Hooks receive JSON data via stdin containing session information and
451
+ event-specific data:
452
+
453
+ ```typescript theme={null}
454
+ {
455
+ // Common fields
456
+ session_id: string
457
+ transcript_path: string // Path to conversation JSON
458
+ cwd: string // The current working directory when the hook is invoked
459
+ permission_mode: string // Current permission mode: "default", "plan", "acceptEdits", "dontAsk", or "bypassPermissions"
460
+
461
+ // Event-specific fields
462
+ hook_event_name: string
463
+ ...
464
+ }
465
+ ```
466
+
467
+ ### PreToolUse Input
468
+
469
+ The exact schema for `tool_input` depends on the tool.
470
+
471
+ ```json theme={null}
472
+ {
473
+ "session_id": "abc123",
474
+ "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
475
+ "cwd": "/Users/...",
476
+ "permission_mode": "default",
477
+ "hook_event_name": "PreToolUse",
478
+ "tool_name": "Write",
479
+ "tool_input": {
480
+ "file_path": "/path/to/file.txt",
481
+ "content": "file content"
482
+ },
483
+ "tool_use_id": "toolu_01ABC123..."
484
+ }
485
+ ```
486
+
487
+ ### PostToolUse Input
488
+
489
+ The exact schema for `tool_input` and `tool_response` depends on the tool.
490
+
491
+ ```json theme={null}
492
+ {
493
+ "session_id": "abc123",
494
+ "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
495
+ "cwd": "/Users/...",
496
+ "permission_mode": "default",
497
+ "hook_event_name": "PostToolUse",
498
+ "tool_name": "Write",
499
+ "tool_input": {
500
+ "file_path": "/path/to/file.txt",
501
+ "content": "file content"
502
+ },
503
+ "tool_response": {
504
+ "filePath": "/path/to/file.txt",
505
+ "success": true
506
+ },
507
+ "tool_use_id": "toolu_01ABC123..."
508
+ }
509
+ ```
510
+
511
+ ### Notification Input
512
+
513
+ ```json theme={null}
514
+ {
515
+ "session_id": "abc123",
516
+ "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
517
+ "cwd": "/Users/...",
518
+ "permission_mode": "default",
519
+ "hook_event_name": "Notification",
520
+ "message": "Claude needs your permission to use Bash",
521
+ "notification_type": "permission_prompt"
522
+ }
523
+ ```
524
+
525
+ ### UserPromptSubmit Input
526
+
527
+ ```json theme={null}
528
+ {
529
+ "session_id": "abc123",
530
+ "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
531
+ "cwd": "/Users/...",
532
+ "permission_mode": "default",
533
+ "hook_event_name": "UserPromptSubmit",
534
+ "prompt": "Write a function to calculate the factorial of a number"
535
+ }
536
+ ```
537
+
538
+ ### Stop and SubagentStop Input
539
+
540
+ `stop_hook_active` is true when Claude Code is already continuing as a result of
541
+ a stop hook. Check this value or process the transcript to prevent Claude Code
542
+ from running indefinitely.
543
+
544
+ ```json theme={null}
545
+ {
546
+ "session_id": "abc123",
547
+ "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
548
+ "permission_mode": "default",
549
+ "hook_event_name": "Stop",
550
+ "stop_hook_active": true
551
+ }
552
+ ```
553
+
554
+ ### PreCompact Input
555
+
556
+ For `manual`, `custom_instructions` comes from what the user passes into
557
+ `/compact`. For `auto`, `custom_instructions` is empty.
558
+
559
+ ```json theme={null}
560
+ {
561
+ "session_id": "abc123",
562
+ "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
563
+ "permission_mode": "default",
564
+ "hook_event_name": "PreCompact",
565
+ "trigger": "manual",
566
+ "custom_instructions": ""
567
+ }
568
+ ```
569
+
570
+ ### SessionStart Input
571
+
572
+ ```json theme={null}
573
+ {
574
+ "session_id": "abc123",
575
+ "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
576
+ "permission_mode": "default",
577
+ "hook_event_name": "SessionStart",
578
+ "source": "startup"
579
+ }
580
+ ```
581
+
582
+ ### SessionEnd Input
583
+
584
+ ```json theme={null}
585
+ {
586
+ "session_id": "abc123",
587
+ "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
588
+ "cwd": "/Users/...",
589
+ "permission_mode": "default",
590
+ "hook_event_name": "SessionEnd",
591
+ "reason": "exit"
592
+ }
593
+ ```
594
+
595
+ ## Hook Output
596
+
597
+ There are two mutually exclusive ways for hooks to return output back to Claude Code. The output
598
+ communicates whether to block and any feedback that should be shown to Claude
599
+ and the user.
600
+
601
+ ### Simple: Exit Code
602
+
603
+ Hooks communicate status through exit codes, stdout, and stderr:
604
+
605
+ * **Exit code 0**: Success. `stdout` is shown to the user in verbose mode
606
+ (ctrl+o), except for `UserPromptSubmit` and `SessionStart`, where stdout is
607
+ added to the context. JSON output in `stdout` is parsed for structured control
608
+ (see [Advanced: JSON Output](#advanced-json-output)).
609
+ * **Exit code 2**: Blocking error. Only `stderr` is used as the error message
610
+ and fed back to Claude. The format is `[command]: {stderr}`. JSON in `stdout`
611
+ is **not** processed for exit code 2. See per-hook-event behavior below.
612
+ * **Other exit codes**: Non-blocking error. `stderr` is shown to the user in verbose mode (ctrl+o) with
613
+ format `Failed with non-blocking status code: {stderr}`. If `stderr` is empty,
614
+ it shows `No stderr output`. Execution continues.
615
+
616
+ <Warning>
617
+ Reminder: Claude Code does not see stdout if the exit code is 0, except for
618
+ the `UserPromptSubmit` hook where stdout is injected as context.
619
+ </Warning>
620
+
621
+ #### Exit Code 2 Behavior
622
+
623
+ | Hook Event | Behavior |
624
+ | ------------------- | ------------------------------------------------------------------ |
625
+ | `PreToolUse` | Blocks the tool call, shows stderr to Claude |
626
+ | `PermissionRequest` | Denies the permission, shows stderr to Claude |
627
+ | `PostToolUse` | Shows stderr to Claude (tool already ran) |
628
+ | `Notification` | N/A, shows stderr to user only |
629
+ | `UserPromptSubmit` | Blocks prompt processing, erases prompt, shows stderr to user only |
630
+ | `Stop` | Blocks stoppage, shows stderr to Claude |
631
+ | `SubagentStop` | Blocks stoppage, shows stderr to Claude subagent |
632
+ | `PreCompact` | N/A, shows stderr to user only |
633
+ | `SessionStart` | N/A, shows stderr to user only |
634
+ | `SessionEnd` | N/A, shows stderr to user only |
635
+
636
+ ### Advanced: JSON Output
637
+
638
+ Hooks can return structured JSON in `stdout` for more sophisticated control.
639
+
640
+ <Warning>
641
+ JSON output is only processed when the hook exits with code 0. If your hook
642
+ exits with code 2 (blocking error), `stderr` text is used directly—any JSON in `stdout`
643
+ is ignored. For other non-zero exit codes, only `stderr` is shown to the user in verbose mode (ctrl+o).
644
+ </Warning>
645
+
646
+ #### Common JSON Fields
647
+
648
+ All hook types can include these optional fields:
649
+
650
+ ```json theme={null}
651
+ {
652
+ "continue": true, // Whether Claude should continue after hook execution (default: true)
653
+ "stopReason": "string", // Message shown when continue is false
654
+
655
+ "suppressOutput": true, // Hide stdout from transcript mode (default: false)
656
+ "systemMessage": "string" // Optional warning message shown to the user
657
+ }
658
+ ```
659
+
660
+ If `continue` is false, Claude stops processing after the hooks run.
661
+
662
+ * For `PreToolUse`, this is different from `"permissionDecision": "deny"`, which
663
+ only blocks a specific tool call and provides automatic feedback to Claude.
664
+ * For `PostToolUse`, this is different from `"decision": "block"`, which
665
+ provides automated feedback to Claude.
666
+ * For `UserPromptSubmit`, this prevents the prompt from being processed.
667
+ * For `Stop` and `SubagentStop`, this takes precedence over any
668
+ `"decision": "block"` output.
669
+ * In all cases, `"continue" = false` takes precedence over any
670
+ `"decision": "block"` output.
671
+
672
+ `stopReason` accompanies `continue` with a reason shown to the user, not shown
673
+ to Claude.
674
+
675
+ #### `PreToolUse` Decision Control
676
+
677
+ `PreToolUse` hooks can control whether a tool call proceeds.
678
+
679
+ * `"allow"` bypasses the permission system. `permissionDecisionReason` is shown
680
+ to the user but not to Claude.
681
+ * `"deny"` prevents the tool call from executing. `permissionDecisionReason` is
682
+ shown to Claude.
683
+ * `"ask"` asks the user to confirm the tool call in the UI.
684
+ `permissionDecisionReason` is shown to the user but not to Claude.
685
+
686
+ Additionally, hooks can modify tool inputs before execution using `updatedInput`:
687
+
688
+ * `updatedInput` allows you to modify the tool's input parameters before the tool executes.
689
+ * This is most useful with `"permissionDecision": "allow"` to modify and approve tool calls.
690
+
691
+ ```json theme={null}
692
+ {
693
+ "hookSpecificOutput": {
694
+ "hookEventName": "PreToolUse",
695
+ "permissionDecision": "allow"
696
+ "permissionDecisionReason": "My reason here",
697
+ "updatedInput": {
698
+ "field_to_modify": "new value"
699
+ }
700
+ }
701
+ }
702
+ ```
703
+
704
+ <Note>
705
+ The `decision` and `reason` fields are deprecated for PreToolUse hooks.
706
+ Use `hookSpecificOutput.permissionDecision` and
707
+ `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields
708
+ `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.
709
+ </Note>
710
+
711
+ #### `PermissionRequest` Decision Control
712
+
713
+ `PermissionRequest` hooks can allow or deny permission requests shown to the user.
714
+
715
+ * For `"behavior": "allow"` you can also optionally pass in an `"updatedInput"` that modifies the tool's input parameters before the tool executes.
716
+ * 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.
717
+
718
+ ```json theme={null}
719
+ {
720
+ "hookSpecificOutput": {
721
+ "hookEventName": "PermissionRequest",
722
+ "decision": {
723
+ "behavior": "allow",
724
+ "updatedInput": {
725
+ "command": "npm run lint"
726
+ }
727
+ }
728
+ }
729
+ }
730
+ ```
731
+
732
+ #### `PostToolUse` Decision Control
733
+
734
+ `PostToolUse` hooks can provide feedback to Claude after tool execution.
735
+
736
+ * `"block"` automatically prompts Claude with `reason`.
737
+ * `undefined` does nothing. `reason` is ignored.
738
+ * `"hookSpecificOutput.additionalContext"` adds context for Claude to consider.
739
+
740
+ ```json theme={null}
741
+ {
742
+ "decision": "block" | undefined,
743
+ "reason": "Explanation for decision",
744
+ "hookSpecificOutput": {
745
+ "hookEventName": "PostToolUse",
746
+ "additionalContext": "Additional information for Claude"
747
+ }
748
+ }
749
+ ```
750
+
751
+ #### `UserPromptSubmit` Decision Control
752
+
753
+ `UserPromptSubmit` hooks can control whether a user prompt is processed and add context.
754
+
755
+ **Adding context (exit code 0):**
756
+ There are two ways to add context to the conversation:
757
+
758
+ 1. **Plain text stdout** (simpler): Any non-JSON text written to stdout is added
759
+ as context. This is the easiest way to inject information.
760
+
761
+ 2. **JSON with `additionalContext`** (structured): Use the JSON format below for
762
+ more control. The `additionalContext` field is added as context.
763
+
764
+ Both methods work with exit code 0. Plain stdout is shown as hook output in
765
+ the transcript; `additionalContext` is added more discretely.
766
+
767
+ **Blocking prompts:**
768
+
769
+ * `"decision": "block"` prevents the prompt from being processed. The submitted
770
+ prompt is erased from context. `"reason"` is shown to the user but not added
771
+ to context.
772
+ * `"decision": undefined` (or omitted) allows the prompt to proceed normally.
773
+
774
+ ```json theme={null}
775
+ {
776
+ "decision": "block" | undefined,
777
+ "reason": "Explanation for decision",
778
+ "hookSpecificOutput": {
779
+ "hookEventName": "UserPromptSubmit",
780
+ "additionalContext": "My additional context here"
781
+ }
782
+ }
783
+ ```
784
+
785
+ <Note>
786
+ 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
787
+ block prompts or want more structured control.
788
+ </Note>
789
+
790
+ #### `Stop`/`SubagentStop` Decision Control
791
+
792
+ `Stop` and `SubagentStop` hooks can control whether Claude must continue.
793
+
794
+ * `"block"` prevents Claude from stopping. You must populate `reason` for Claude
795
+ to know how to proceed.
796
+ * `undefined` allows Claude to stop. `reason` is ignored.
797
+
798
+ ```json theme={null}
799
+ {
800
+ "decision": "block" | undefined,
801
+ "reason": "Must be provided when Claude is blocked from stopping"
802
+ }
803
+ ```
804
+
805
+ #### `SessionStart` Decision Control
806
+
807
+ `SessionStart` hooks allow you to load in context at the start of a session.
808
+
809
+ * `"hookSpecificOutput.additionalContext"` adds the string to the context.
810
+ * Multiple hooks' `additionalContext` values are concatenated.
811
+
812
+ ```json theme={null}
813
+ {
814
+ "hookSpecificOutput": {
815
+ "hookEventName": "SessionStart",
816
+ "additionalContext": "My additional context here"
817
+ }
818
+ }
819
+ ```
820
+
821
+ #### `SessionEnd` Decision Control
822
+
823
+ `SessionEnd` hooks run when a session ends. They cannot block session termination
824
+ but can perform cleanup tasks.
825
+
826
+ #### Exit Code Example: Bash Command Validation
827
+
828
+ ```python theme={null}
829
+ #!/usr/bin/env python3
830
+ import json
831
+ import re
832
+ import sys
833
+
834
+ # Define validation rules as a list of (regex pattern, message) tuples
835
+ VALIDATION_RULES = [
836
+ (
837
+ r"\bgrep\b(?!.*\|)",
838
+ "Use 'rg' (ripgrep) instead of 'grep' for better performance and features",
839
+ ),
840
+ (
841
+ r"\bfind\s+\S+\s+-name\b",
842
+ "Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",
843
+ ),
844
+ ]
845
+
846
+
847
+ def validate_command(command: str) -> list[str]:
848
+ issues = []
849
+ for pattern, message in VALIDATION_RULES:
850
+ if re.search(pattern, command):
851
+ issues.append(message)
852
+ return issues
853
+
854
+
855
+ try:
856
+ input_data = json.load(sys.stdin)
857
+ except json.JSONDecodeError as e:
858
+ print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
859
+ sys.exit(1)
860
+
861
+ tool_name = input_data.get("tool_name", "")
862
+ tool_input = input_data.get("tool_input", {})
863
+ command = tool_input.get("command", "")
864
+
865
+ if tool_name != "Bash" or not command:
866
+ sys.exit(1)
867
+
868
+ # Validate the command
869
+ issues = validate_command(command)
870
+
871
+ if issues:
872
+ for message in issues:
873
+ print(f"• {message}", file=sys.stderr)
874
+ # Exit code 2 blocks tool call and shows stderr to Claude
875
+ sys.exit(2)
876
+ ```
877
+
878
+ #### JSON Output Example: UserPromptSubmit to Add Context and Validation
879
+
880
+ <Note>
881
+ For `UserPromptSubmit` hooks, you can inject context using either method:
882
+
883
+ * **Plain text stdout** with exit code 0: Simplest approach, prints text
884
+ * **JSON output** with exit code 0: Use `"decision": "block"` to reject prompts,
885
+ or `additionalContext` for structured context injection
886
+
887
+ Remember: Exit code 2 only uses `stderr` for the error message. To block using
888
+ JSON (with a custom reason), use `"decision": "block"` with exit code 0.
889
+ </Note>
890
+
891
+ ```python theme={null}
892
+ #!/usr/bin/env python3
893
+ import json
894
+ import sys
895
+ import re
896
+ import datetime
897
+
898
+ # Load input from stdin
899
+ try:
900
+ input_data = json.load(sys.stdin)
901
+ except json.JSONDecodeError as e:
902
+ print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
903
+ sys.exit(1)
904
+
905
+ prompt = input_data.get("prompt", "")
906
+
907
+ # Check for sensitive patterns
908
+ sensitive_patterns = [
909
+ (r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),
910
+ ]
911
+
912
+ for pattern, message in sensitive_patterns:
913
+ if re.search(pattern, prompt):
914
+ # Use JSON output to block with a specific reason
915
+ output = {
916
+ "decision": "block",
917
+ "reason": f"Security policy violation: {message}. Please rephrase your request without sensitive information."
918
+ }
919
+ print(json.dumps(output))
920
+ sys.exit(0)
921
+
922
+ # Add current time to context
923
+ context = f"Current time: {datetime.datetime.now()}"
924
+ print(context)
925
+
926
+ """
927
+ The following is also equivalent:
928
+ print(json.dumps({
929
+ "hookSpecificOutput": {
930
+ "hookEventName": "UserPromptSubmit",
931
+ "additionalContext": context,
932
+ },
933
+ }))
934
+ """
935
+
936
+ # Allow the prompt to proceed with the additional context
937
+ sys.exit(0)
938
+ ```
939
+
940
+ #### JSON Output Example: PreToolUse with Approval
941
+
942
+ ```python theme={null}
943
+ #!/usr/bin/env python3
944
+ import json
945
+ import sys
946
+
947
+ # Load input from stdin
948
+ try:
949
+ input_data = json.load(sys.stdin)
950
+ except json.JSONDecodeError as e:
951
+ print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
952
+ sys.exit(1)
953
+
954
+ tool_name = input_data.get("tool_name", "")
955
+ tool_input = input_data.get("tool_input", {})
956
+
957
+ # Example: Auto-approve file reads for documentation files
958
+ if tool_name == "Read":
959
+ file_path = tool_input.get("file_path", "")
960
+ if file_path.endswith((".md", ".mdx", ".txt", ".json")):
961
+ # Use JSON output to auto-approve the tool call
962
+ output = {
963
+ "decision": "approve",
964
+ "reason": "Documentation file auto-approved",
965
+ "suppressOutput": True # Don't show in verbose mode
966
+ }
967
+ print(json.dumps(output))
968
+ sys.exit(0)
969
+
970
+ # For other cases, let the normal permission flow proceed
971
+ sys.exit(0)
972
+ ```
973
+
974
+ ## Working with MCP Tools
975
+
976
+ Claude Code hooks work seamlessly with
977
+ [Model Context Protocol (MCP) tools](/en/mcp). When MCP servers
978
+ provide tools, they appear with a special naming pattern that you can match in
979
+ your hooks.
980
+
981
+ ### MCP Tool Naming
982
+
983
+ MCP tools follow the pattern `mcp__<server>__<tool>`, for example:
984
+
985
+ * `mcp__memory__create_entities` - Memory server's create entities tool
986
+ * `mcp__filesystem__read_file` - Filesystem server's read file tool
987
+ * `mcp__github__search_repositories` - GitHub server's search tool
988
+
989
+ ### Configuring Hooks for MCP Tools
990
+
991
+ You can target specific MCP tools or entire MCP servers:
992
+
993
+ ```json theme={null}
994
+ {
995
+ "hooks": {
996
+ "PreToolUse": [
997
+ {
998
+ "matcher": "mcp__memory__.*",
999
+ "hooks": [
1000
+ {
1001
+ "type": "command",
1002
+ "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
1003
+ }
1004
+ ]
1005
+ },
1006
+ {
1007
+ "matcher": "mcp__.*__write.*",
1008
+ "hooks": [
1009
+ {
1010
+ "type": "command",
1011
+ "command": "/home/user/scripts/validate-mcp-write.py"
1012
+ }
1013
+ ]
1014
+ }
1015
+ ]
1016
+ }
1017
+ }
1018
+ ```
1019
+
1020
+ ## Examples
1021
+
1022
+ <Tip>
1023
+ For practical examples including code formatting, notifications, and file protection, see [More Examples](/en/hooks-guide#more-examples) in the get started guide.
1024
+ </Tip>
1025
+
1026
+ ## Security Considerations
1027
+
1028
+ ### Disclaimer
1029
+
1030
+ **USE AT YOUR OWN RISK**: Claude Code hooks execute arbitrary shell commands on
1031
+ your system automatically. By using hooks, you acknowledge that:
1032
+
1033
+ * You are solely responsible for the commands you configure
1034
+ * Hooks can modify, delete, or access any files your user account can access
1035
+ * Malicious or poorly written hooks can cause data loss or system damage
1036
+ * Anthropic provides no warranty and assumes no liability for any damages
1037
+ resulting from hook usage
1038
+ * You should thoroughly test hooks in a safe environment before production use
1039
+
1040
+ Always review and understand any hook commands before adding them to your
1041
+ configuration.
1042
+
1043
+ ### Security Best Practices
1044
+
1045
+ Here are some key practices for writing more secure hooks:
1046
+
1047
+ 1. **Validate and sanitize inputs** - Never trust input data blindly
1048
+ 2. **Always quote shell variables** - Use `"$VAR"` not `$VAR`
1049
+ 3. **Block path traversal** - Check for `..` in file paths
1050
+ 4. **Use absolute paths** - Specify full paths for scripts (use
1051
+ "\$CLAUDE\_PROJECT\_DIR" for the project path)
1052
+ 5. **Skip sensitive files** - Avoid `.env`, `.git/`, keys, etc.
1053
+
1054
+ ### Configuration Safety
1055
+
1056
+ Direct edits to hooks in settings files don't take effect immediately. Claude
1057
+ Code:
1058
+
1059
+ 1. Captures a snapshot of hooks at startup
1060
+ 2. Uses this snapshot throughout the session
1061
+ 3. Warns if hooks are modified externally
1062
+ 4. Requires review in `/hooks` menu for changes to apply
1063
+
1064
+ This prevents malicious hook modifications from affecting your current session.
1065
+
1066
+ ## Hook Execution Details
1067
+
1068
+ * **Timeout**: 60-second execution limit by default, configurable per command.
1069
+ * A timeout for an individual command does not affect the other commands.
1070
+ * **Parallelization**: All matching hooks run in parallel
1071
+ * **Deduplication**: Multiple identical hook commands are deduplicated automatically
1072
+ * **Environment**: Runs in current directory with Claude Code's environment
1073
+ * The `CLAUDE_PROJECT_DIR` environment variable is available and contains the
1074
+ absolute path to the project root directory (where Claude Code was started)
1075
+ * 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.
1076
+ * **Input**: JSON via stdin
1077
+ * **Output**:
1078
+ * PreToolUse/PermissionRequest/PostToolUse/Stop/SubagentStop: Progress shown in verbose mode (ctrl+o)
1079
+ * Notification/SessionEnd: Logged to debug only (`--debug`)
1080
+ * UserPromptSubmit/SessionStart: stdout added as context for Claude
1081
+
1082
+ ## Debugging
1083
+
1084
+ ### Basic Troubleshooting
1085
+
1086
+ If your hooks aren't working:
1087
+
1088
+ 1. **Check configuration** - Run `/hooks` to see if your hook is registered
1089
+ 2. **Verify syntax** - Ensure your JSON settings are valid
1090
+ 3. **Test commands** - Run hook commands manually first
1091
+ 4. **Check permissions** - Make sure scripts are executable
1092
+ 5. **Review logs** - Use `claude --debug` to see hook execution details
1093
+
1094
+ Common issues:
1095
+
1096
+ * **Quotes not escaped** - Use `\"` inside JSON strings
1097
+ * **Wrong matcher** - Check tool names match exactly (case-sensitive)
1098
+ * **Command not found** - Use full paths for scripts
1099
+
1100
+ ### Advanced Debugging
1101
+
1102
+ For complex hook issues:
1103
+
1104
+ 1. **Inspect hook execution** - Use `claude --debug` to see detailed hook
1105
+ execution
1106
+ 2. **Validate JSON schemas** - Test hook input/output with external tools
1107
+ 3. **Check environment variables** - Verify Claude Code's environment is correct
1108
+ 4. **Test edge cases** - Try hooks with unusual file paths or inputs
1109
+ 5. **Monitor system resources** - Check for resource exhaustion during hook
1110
+ execution
1111
+ 6. **Use structured logging** - Implement logging in your hook scripts
1112
+
1113
+ ### Debug Output Example
1114
+
1115
+ Use `claude --debug` to see hook execution details:
1116
+
1117
+ ```
1118
+ [DEBUG] Executing hooks for PostToolUse:Write
1119
+ [DEBUG] Getting matching hook commands for PostToolUse with query: Write
1120
+ [DEBUG] Found 1 hook matchers in settings
1121
+ [DEBUG] Matched 1 hooks for query "Write"
1122
+ [DEBUG] Found 1 hook commands to execute
1123
+ [DEBUG] Executing hook command: <Your command> with timeout 60000ms
1124
+ [DEBUG] Hook command completed with status 0: <Your stdout>
1125
+ ```
1126
+
1127
+ Progress messages appear in verbose mode (ctrl+o) showing:
1128
+
1129
+ * Which hook is running
1130
+ * Command being executed
1131
+ * Success/failure status
1132
+ * Output or error messages
1133
+
1134
+
1135
+ ---
1136
+
1137
+ > To find navigation and other pages in this documentation, fetch the llms.txt file at: https://code.claude.com/docs/llms.txt