claude_hooks 1.3.0 → 1.4.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/README.md +19 -15
  4. data/docs/API/COMMON.md +4 -3
  5. data/docs/API/NOTIFICATION.md +5 -4
  6. data/docs/API/PERMISSION_DENIED.md +4 -0
  7. data/docs/API/PERMISSION_REQUEST.md +3 -3
  8. data/docs/API/POST_MODEL_SWITCH.md +53 -0
  9. data/docs/API/POST_TOOL_USE.md +7 -0
  10. data/docs/API/POST_TOOL_USE_FAILURE.md +1 -0
  11. data/docs/API/PRE_MODEL_SWITCH.md +67 -0
  12. data/docs/API/PRE_TOOL_USE.md +1 -0
  13. data/docs/API/SESSION_START.md +7 -1
  14. data/docs/API/SETUP.md +3 -3
  15. data/docs/API/STOP.md +6 -0
  16. data/docs/API/SUBAGENT_STOP.md +10 -0
  17. data/docs/API/TASK_CREATED.md +9 -7
  18. data/docs/API/USER_PROMPT_SUBMIT.md +14 -0
  19. data/docs/API/WORKTREE_REMOVE.md +9 -4
  20. data/docs/external/claude-hooks-reference.md +973 -294
  21. data/docs/mitts/prd.md +8 -0
  22. data/docs/mitts/setup.md +5 -3
  23. data/lib/claude_hooks/base.rb +12 -0
  24. data/lib/claude_hooks/output/base.rb +4 -0
  25. data/lib/claude_hooks/output/notification.rb +10 -1
  26. data/lib/claude_hooks/output/post_model_switch.rb +50 -0
  27. data/lib/claude_hooks/output/post_tool_batch.rb +10 -0
  28. data/lib/claude_hooks/output/post_tool_use.rb +23 -5
  29. data/lib/claude_hooks/output/pre_model_switch.rb +121 -0
  30. data/lib/claude_hooks/output/pre_tool_use.rb +8 -0
  31. data/lib/claude_hooks/output/task_created.rb +34 -1
  32. data/lib/claude_hooks/output/user_prompt_expansion.rb +10 -0
  33. data/lib/claude_hooks/output/user_prompt_submit.rb +19 -6
  34. data/lib/claude_hooks/output/worktree_remove.rb +4 -2
  35. data/lib/claude_hooks/permission_denied.rb +7 -0
  36. data/lib/claude_hooks/permission_request.rb +10 -1
  37. data/lib/claude_hooks/post_model_switch.rb +70 -0
  38. data/lib/claude_hooks/post_tool_use.rb +14 -0
  39. data/lib/claude_hooks/post_tool_use_failure.rb +7 -0
  40. data/lib/claude_hooks/pre_model_switch.rb +90 -0
  41. data/lib/claude_hooks/pre_tool_use.rb +7 -0
  42. data/lib/claude_hooks/session_start.rb +18 -0
  43. data/lib/claude_hooks/setup.rb +3 -0
  44. data/lib/claude_hooks/task_created.rb +17 -0
  45. data/lib/claude_hooks/user_prompt_submit.rb +26 -5
  46. data/lib/claude_hooks/version.rb +1 -1
  47. data/lib/claude_hooks.rb +4 -0
  48. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee18a384c04bcfff4fa734935277a938e14d2799646369e8db8e013c5a7b33ce
4
- data.tar.gz: b784bc4cc4e52096b563c25a2c6d19175a38ac37e6ea72d110c32c3887566128
3
+ metadata.gz: cca02ef252dc6564f109b8f4a09e7ef7c107695dbfb76bafbf44313215f1713e
4
+ data.tar.gz: f8d90408c943f7b0a8b91d9d067557bafb015dfa33943a369839badb189c5374
5
5
  SHA512:
6
- metadata.gz: 25345ee635cc383661393b66bca8d82f476615e311fb07d3206630b4cc13b9972c62cb08efe2d0ba03ab053ce40f07bdac2dfa3b0d633377150474ba37743e5d
7
- data.tar.gz: ed9511e85a7a74e9ea851e5ece032afcb6c860f48b52143da80fc999a81dce35374ff1df3fca1914d2c91963578c7f7561d94890bb6f39bf23e530599b2a107e
6
+ metadata.gz: dba4a8356c7a11faedeb51d5ff8cf03bdf70e365ee0b922f9b11e779d24d13037469d2f3bbf0facedf555bb031a35ddffea06e9043f6375276a4faf9bc394161
7
+ data.tar.gz: b1a5334e3a2001a567063b8b896b4fac0debea9e344f0491569902c96eb31f3a8b793b50b67d1bbb1aa0ba0619d9fb0f5183b70c0146107685aa16f54b3708c6
data/CHANGELOG.md CHANGED
@@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.4.0] - 2026-10-14
9
+
10
+ Spec-parity update tracking the Claude Code hooks documentation changes analyzed in issues #61, #62, #63, #65, #69, #70, and #71. (Issue #64 required no changes; #70 is docs-only.)
11
+
12
+ ### Added
13
+
14
+ - **`PreModelSwitch` hook event** (`ClaudeHooks::PreModelSwitch` / `ClaudeHooks::Output::PreModelSwitch`): runs before a requested model switch (Claude Code v2.1.251+). Decision-control hook modeled on `PreToolUse` — builders `approve!`/`allow!`, `deny!`/`block_switch!`, `ask!` (via `permissionDecision`), plus a top-level `block!(reason)` (`decision: "block"`). Input readers: `from_model`, `to_model`, `requested_model`, `source`, `context_tokens`, `prompt_cache_warm`, `cache_ttl`, `estimated_cache_write_usd`, `pricing`. Output accessors `allowed?`/`denied?`/`blocked?`/`should_ask?`, `permission_decision`, `permission_reason`, `decision`, `reason`. Always exits 0 (JSON API); merge precedence `deny > ask > allow`, a top-level block makes the merged decision `deny`, and the merge only emits a `permissionDecision` when some hook set one.
15
+ - **`PostModelSwitch` hook event** (`ClaudeHooks::PostModelSwitch` / `ClaudeHooks::Output::PostModelSwitch`): runs after the session's model changes (Claude Code v2.1.251+). Context-only (can't block); builder `add_additional_context!` / `add_context!`, output accessor `additional_context`. Same input readers as `PreModelSwitch`, with extra `source` values `"auto"`/`"resume"`.
16
+ - **`PostToolUse#classifier_context!(note)`** and **`Output::PostToolUse#classifier_context`**: set/read `hookSpecificOutput.classifierContext`, a note for the auto-mode classifier (Claude Code v2.1.236+). Carried through multi-handler merge.
17
+ - **`TaskCreated#block!(reason)`** / **`allow!`** and **`Output::TaskCreated#decision`/`#reason`/`#blocked?`**: block task creation via a top-level `decision: "block"` (the `reason` is returned to Claude as the task-creation error).
18
+ - **`scratchpad_dir` common input reader** on `ClaudeHooks::Base` (Claude Code v2.1.257+). Not added to `COMMON_INPUT_FIELDS` — it is version-gated and often absent, so it never triggers the missing-field warning.
19
+ - **`mcp_server` input reader** on `PreToolUse`, `PermissionRequest`, `PostToolUse`, `PostToolUseFailure`, and `PermissionDenied` (Claude Code v2.1.274+): for MCP tools, a hash with the server's `name` and `source` (where its definition came from). `nil` for non-MCP tools; not added to `input_fields`.
20
+ - **`UserPromptSubmit` session title and prompt suppression**: `session_title` input reader; `session_title!(title)` and `suppress_original_prompt!` builders (`hookSpecificOutput.sessionTitle` / `suppressOriginalPrompt`); `output.session_title` / `output.suppress_original_prompt?` accessors, both carried through merge (last title wins; suppression wins if any hook asks). `suppress_original_prompt!` keeps a blocked prompt's text out of the block message only; the text can still appear in the session transcript and prompt history.
21
+ - **`SessionStart` resume/fork cost fields**: readers `seconds_since_last_response`, `context_tokens`, `prompt_cache_likely_expired`, `estimated_cache_write_usd` (present when `source` is `"resume"`/`"fork"`).
22
+
23
+ ### Changed
24
+
25
+ - **`Notification` now always exits 0.** Claude Code ignores a Notification hook's exit code and stderr, so `Output::Notification` no longer exits 2 on `continue: false` — it always exits 0 and writes JSON to stdout, keeping the `terminal_sequence!` (desktop-notification) pattern working.
26
+ - **`WorktreeRemove` exit code now reports failure.** Upstream reversed the contract: a non-zero exit now fails the worktree removal when the directory still exists (previously the exit code was ignored). With a `WorktreeRemove` hook configured, the hook itself does the cleanup (exit 0 means "counts as removed") and its JSON output is discarded. `Output::WorktreeRemove#exit_code` now follows `continue` (0/2) instead of hard-coding 0; use `prevent_continue!(reason)` to block. Note that a `WorktreeRemove` hook that raises now also leaves the worktree on disk, since `CLI.run_hook` exits 1 on errors and Claude Code fails the removal on any non-zero exit.
27
+ - **`UserPromptSubmit#add_additional_context!` / `#empty_additional_context!` no longer wipe other `hookSpecificOutput` fields**, so they compose with the new title/suppression builders in any order. Output for existing single-call usage is unchanged.
28
+ - **`TaskCreated` `continue: false` is ignored** by Claude Code; block via the new `block!` (top-level `decision`) or exit 2 instead.
29
+ - **Docs**: `Stop`/`SubagentStop` input tables now list the inherited `last_assistant_message`, `background_tasks`, and `session_crons` readers; notes added for the stop-hook continuation cap (`CLAUDE_CODE_STOP_HOOK_BLOCK_CAP`), SubagentStop firing for internal agents, and expanded pasted content in `UserPromptSubmit#prompt`.
30
+
31
+ ### Fixed
32
+
33
+ - **`Output::PostToolUse.merge` no longer drops rewritten tool output.** `updatedToolOutput` / `updatedMCPToolOutput` (from `update_tool_output!` / `update_mcp_tool_output!`) were lost when merged with any other output, so e.g. a redacting hook's rewrite was silently discarded when composed with a context hook. The last non-nil rewrite now wins, as with `PreToolUse`'s `updatedInput`.
34
+ - **`additionalContext` survives merging on `PreToolUse`, `PostToolBatch`, and `UserPromptExpansion`.** These merges dropped it; contexts are now joined with `"\n\n"` like the other context-carrying events.
35
+ - **`PermissionRequest` no longer warns about a missing `tool_use_id`.** Claude Code doesn't send that field to PermissionRequest hooks, so it was removed from `input_fields` (and the README/API docs); every real invocation used to log a spurious missing-field warning.
36
+
37
+ ### Deprecated
38
+
39
+ - **`suppressOutput` is a no-op.** `suppress_output!` / `show_output!` / `output.suppress_output?` remain callable for backward compatibility, but Claude Code accepts the field without acting on it.
40
+ - **`Setup` output is discarded.** `Setup#add_additional_context!` remains callable but Claude Code discards all Setup JSON output; use a `SessionStart` hook to inject context instead.
41
+ - **`PermissionRequest#tool_use_id`** always returns `nil` (Claude Code doesn't send the field); kept callable for backward compatibility.
42
+
43
+ ### Notes
44
+
45
+ - Total hook event classes: 33 (was 31).
46
+ - No DSL methods removed — deprecated helpers stay callable as no-ops.
47
+ - All test files pass (`ruby test/run_all_tests.rb`). Two existing tests that used `Notification` as an "exits 2 on `continue: false`" example were retargeted to `PreCompact`, and the `WorktreeRemove` exit-code test was rewritten for the new blocking contract.
48
+
8
49
  ## [1.3.0] - 2026-08-15
9
50
 
10
51
  ### Added
data/README.md CHANGED
@@ -295,7 +295,9 @@ The framework supports the following hook types:
295
295
  | **[Elicitation](docs/API/ELICITATION.md)** | `ClaudeHooks::Elicitation` | Runs when an MCP server requests user input |
296
296
  | **[ElicitationResult](docs/API/ELICITATION_RESULT.md)** | `ClaudeHooks::ElicitationResult` | Runs after an elicitation response is provided |
297
297
  | **[WorktreeCreate](docs/API/WORKTREE_CREATE.md)** | `ClaudeHooks::WorktreeCreate` | Runs when Claude Code creates a git worktree |
298
- | **[WorktreeRemove](docs/API/WORKTREE_REMOVE.md)** | `ClaudeHooks::WorktreeRemove` | Runs when a git worktree is removed |
298
+ | **[WorktreeRemove](docs/API/WORKTREE_REMOVE.md)** | `ClaudeHooks::WorktreeRemove` | Runs when a git worktree is removed; can block the removal |
299
+ | **[PreModelSwitch](docs/API/PRE_MODEL_SWITCH.md)** | `ClaudeHooks::PreModelSwitch` | Runs before a requested model switch; can allow, deny, ask, or block it |
300
+ | **[PostModelSwitch](docs/API/POST_MODEL_SWITCH.md)** | `ClaudeHooks::PostModelSwitch` | Runs after the session's model changes; adds context (can't block) |
299
301
  | **[SessionEnd](docs/API/SESSION_END.md)** | `ClaudeHooks::SessionEnd` | Runs when a Claude Code session ends |
300
302
 
301
303
  ## 🚀 Claude Hook Flow
@@ -394,23 +396,23 @@ The framework supports all existing hook types with their respective input field
394
396
 
395
397
  | Hook Type | Input Fields |
396
398
  |-----------|--------------|
397
- | **Common (all hooks)** | `session_id`, `transcript_path`, `cwd`, `hook_event_name`, `permission_mode`, `prompt_id`, `agent_id`, `agent_type`, `effort` |
398
- | **SessionStart** | `source`, `model`, `session_title` |
399
+ | **Common (all hooks)** | `session_id`, `transcript_path`, `cwd`, `hook_event_name`, `permission_mode`, `prompt_id`, `agent_id`, `agent_type`, `effort`, `scratchpad_dir` |
400
+ | **SessionStart** | `source`, `model`, `session_title` + on resume/fork: `seconds_since_last_response`, `context_tokens`, `prompt_cache_likely_expired`, `estimated_cache_write_usd` |
399
401
  | **Setup** | `source` |
400
- | **UserPromptSubmit** | `prompt` |
402
+ | **UserPromptSubmit** | `prompt`, `session_title` |
401
403
  | **UserPromptExpansion** | `expansion_type`, `command_name`, `command_args`, `command_source`, `prompt` |
402
404
  | **Notification** | `message`, `notification_type` |
403
405
  | **MessageDisplay** | `turn_id`, `message_id`, `index`, `final`, `delta` |
404
- | **PreToolUse** | `tool_name`, `tool_input`, `tool_use_id` |
405
- | **PermissionRequest** | `tool_name`, `tool_input`, `tool_use_id`, `permission_suggestions` |
406
- | **PermissionDenied** | `tool_name`, `tool_input`, `tool_use_id`, `reason` |
407
- | **PostToolUse** | `tool_name`, `tool_input`, `tool_response`, `tool_use_id` |
406
+ | **PreToolUse** | `tool_name`, `tool_input`, `tool_use_id`, `mcp_server` (MCP tools) |
407
+ | **PermissionRequest** | `tool_name`, `tool_input`, `permission_suggestions`, `mcp_server` (MCP tools) |
408
+ | **PermissionDenied** | `tool_name`, `tool_input`, `tool_use_id`, `reason`, `mcp_server` (MCP tools) |
409
+ | **PostToolUse** | `tool_name`, `tool_input`, `tool_response`, `tool_use_id`, `mcp_server` (MCP tools) |
408
410
  | **PostToolBatch** | `tool_calls` |
409
- | **PostToolUseFailure** | `tool_name`, `tool_input`, `tool_use_id`, `error`, `is_interrupt`, `duration_ms` |
411
+ | **PostToolUseFailure** | `tool_name`, `tool_input`, `tool_use_id`, `error`, `is_interrupt`, `duration_ms`, `mcp_server` (MCP tools) |
410
412
  | **Stop** | `stop_hook_active`, `last_assistant_message`, `background_tasks`, `session_crons` |
411
413
  | **StopFailure** | `error`, `error_details`, `last_assistant_message` |
412
414
  | **SubagentStart** | *(common only: `agent_id`, `agent_type`)* |
413
- | **SubagentStop** | `stop_hook_active`, `agent_transcript_path` + common `agent_id`/`agent_type` |
415
+ | **SubagentStop** | `stop_hook_active`, `last_assistant_message`, `background_tasks`, `session_crons`, `agent_transcript_path` + common `agent_id`/`agent_type` |
414
416
  | **TaskCreated** | `task_id`, `task_subject`, `task_description`, `teammate_name`, `team_name` |
415
417
  | **TaskCompleted** | `task_id`, `task_subject`, `task_description`, `teammate_name`, `team_name` |
416
418
  | **TeammateIdle** | `teammate_name`, `team_name` |
@@ -425,6 +427,8 @@ The framework supports all existing hook types with their respective input field
425
427
  | **ElicitationResult** | `mcp_server_name`, `action`, `mode`, `elicitation_id`, `content` |
426
428
  | **WorktreeCreate** | `name` |
427
429
  | **WorktreeRemove** | `worktree_path` |
430
+ | **PreModelSwitch** | `from_model`, `to_model`, `requested_model`, `source`, `context_tokens`, `prompt_cache_warm`, `cache_ttl`, `estimated_cache_write_usd`, `pricing` |
431
+ | **PostModelSwitch** | `from_model`, `to_model`, `requested_model`, `source`, `context_tokens`, `prompt_cache_warm`, `cache_ttl`, `estimated_cache_write_usd`, `pricing` |
428
432
  | **SessionEnd** | `reason` |
429
433
 
430
434
  ### Hooks API
@@ -648,11 +652,11 @@ Claude Code hooks support multiple exit codes with different behaviors depending
648
652
  | SessionEnd | Operation continues<br/><br />Logged to debug only (`--debug`) | Non-blocking error<br/><br />Logged to debug only (`--debug`) | N/A<br/><br />Logged to debug only (`--debug`) |
649
653
 
650
654
  > [!NOTE]
651
- > The 20 events added in `1.2.0` follow the same families. Their per-event exit-code behavior is documented on each API page under [`docs/API/`](docs/API/):
652
- > - **Blocking via top-level `decision`** (behave like `PreToolUse`/`Stop`): `UserPromptExpansion`, `PostToolBatch`, `ConfigChange`.
653
- > - **Blocking via `exit 2` / `continue: false`** (no `decision` field): `TaskCreated`, `TaskCompleted`, `TeammateIdle`.
654
- > - **JSON-API special** (always `exit 0`, decision in `hookSpecificOutput`): `PermissionDenied`, `Elicitation`, `ElicitationResult`, `WorktreeCreate` (bare-path stdout).
655
- > - **Non-blocking / context-only** (exit code effectively ignored): `Setup`, `SubagentStart`, `PostToolUseFailure`, `StopFailure`, `PostCompact`, `CwdChanged`, `DirectoryAdded`, `FileChanged`, `InstructionsLoaded`, `WorktreeRemove`, `MessageDisplay`.
655
+ > The additional events follow the same families. Their per-event exit-code behavior is documented on each API page under [`docs/API/`](docs/API/):
656
+ > - **Blocking via top-level `decision`** (behave like `PreToolUse`/`Stop`): `UserPromptExpansion`, `PostToolBatch`, `ConfigChange`, `TaskCreated` (also honors `exit 2`).
657
+ > - **Blocking via `exit 2` / `continue: false`** (no `decision` field): `TaskCompleted`, `TeammateIdle`, `WorktreeRemove` (a non-zero exit now fails the removal).
658
+ > - **JSON-API special** (always `exit 0`, decision in `hookSpecificOutput`): `PermissionDenied`, `Elicitation`, `ElicitationResult`, `WorktreeCreate` (bare-path stdout), `PreModelSwitch` (allow/deny/ask, also honors top-level `decision`/`exit 2`).
659
+ > - **Non-blocking / context-only** (exit code effectively ignored): `Setup`, `SubagentStart`, `PostToolUseFailure`, `StopFailure`, `PostCompact`, `CwdChanged`, `DirectoryAdded`, `FileChanged`, `InstructionsLoaded`, `MessageDisplay`, `Notification` (exit code **and** stderr ignored), `PostModelSwitch` (stdout added as context).
656
660
 
657
661
 
658
662
  #### Manually outputing and exiting example with success
data/docs/API/COMMON.md CHANGED
@@ -17,6 +17,7 @@ Input helpers to access the data provided by Claude Code through `STDIN`.
17
17
  | `agent_id` | Get the agent ID (present in subagent contexts) |
18
18
  | `agent_type` | Get the agent type (present in subagent contexts) |
19
19
  | `effort` | Get the effort level string (reads `effort.level`) |
20
+ | `scratchpad_dir` | Path to the session's scratchpad directory. Absent (`nil`) when there is no scratchpad or the temp dir is unavailable (Claude Code v2.1.257+) |
20
21
  | `read_transcript` | Read the transcript file |
21
22
  | `transcript` | Alias for `read_transcript` |
22
23
 
@@ -27,8 +28,8 @@ Hook state methods are helpers to modify the hook's internal state (`output_data
27
28
  |--------|-------------|
28
29
  | `allow_continue!` | Allow Claude to continue (default) |
29
30
  | `prevent_continue!(reason)` | Stop Claude with reason |
30
- | `suppress_output!` | Hide stdout from transcript |
31
- | `show_output!` | Show stdout in transcript (default) |
31
+ | `suppress_output!` | **Deprecated / no-op.** Claude Code accepts `suppressOutput` but no longer acts on it. Safe to call; has no effect |
32
+ | `show_output!` | **Deprecated / no-op** (see `suppress_output!`) |
32
33
  | `clear_specifics!` | Clear hook-specific output |
33
34
  | `system_message!(message)` | Set a system message shown to the user (not to Claude) |
34
35
  | `clear_system_message!` | Clear the system message |
@@ -47,7 +48,7 @@ This object provides helpers to access output data, for merging multiple outputs
47
48
  | `output_data` | RAW output data accessor |
48
49
  | `output.continue?` | Check if Claude should continue processing |
49
50
  | `output.stop_reason` | Get the stop reason if continue is false |
50
- | `output.suppress_output?` | Check if output should be suppressed from transcript |
51
+ | `output.suppress_output?` | Check the `suppressOutput` flag (deprecated — Claude Code ignores it) |
51
52
  | `output.hook_specific_output` | Get the hook-specific output data |
52
53
  | `output.system_message` | Get the system message if any |
53
54
  | `output.terminal_sequence` | Get the terminal escape sequence (if set) |
@@ -11,7 +11,7 @@ Input helpers to access the data provided by Claude Code through `STDIN`.
11
11
  |--------|-------------|
12
12
  | `message` | Get the notification message content |
13
13
  | `notification_message` | Alias for `message` |
14
- | `notification_type` | Get the notification type: `'permission_prompt'`, `'idle_prompt'`, `'auth_success'`, `'elicitation_dialog'` |
14
+ | `notification_type` | Get the notification type: `'permission_prompt'`, `'idle_prompt'`, `'auth_success'`, `'elicitation_dialog'`, `'elicitation_url_dialog'`, `'elicitation_complete'`, `'elicitation_response'`, `'agent_needs_input'`, `'agent_completed'`, `'quota_auto_resume_fired'`, `'quota_auto_resume_stale'`, `'quota_auto_resume_disabled'` |
15
15
 
16
16
  ## Hook State Helpers
17
17
  Notifications are outside facing and do not have any specific state to modify.
@@ -26,8 +26,9 @@ Notifications don't have any specific hook state and thus doesn't have any speci
26
26
 
27
27
  ## Hook Exit Codes
28
28
 
29
+ Claude Code **ignores** a Notification hook's exit code and stderr, and discards `systemMessage`/`continue`, while still emitting `terminalSequence`. `ClaudeHooks::Output::Notification` therefore always exits `0` and writes JSON to stdout — even after `prevent_continue!` — so the `terminal_sequence!` (desktop-notification) pattern keeps working.
30
+
29
31
  | Exit Code | Behavior |
30
32
  |-----------|----------|
31
- | `exit 0` | Operation continues<br/>Logged to debug only (`--debug`) |
32
- | `exit 1` | Non-blocking error<br/>Logged to debug only (`--debug`) |
33
- | `exit 2` | N/A<br/>Logged to debug only (`--debug`) |
33
+ | `exit 0` | Ignored — always the effective exit code |
34
+ | any other | Ignored (exit code and stderr are discarded) |
@@ -13,6 +13,7 @@ Runs when a permission request is denied. Can request that Claude Code retries t
13
13
  | `tool_name` | The name of the tool whose permission was denied |
14
14
  | `tool_input` | The input data for the tool |
15
15
  | `tool_use_id` | The unique identifier for this tool use |
16
+ | `mcp_server` | For MCP tools, a hash with the server's `name` and `source` (where its definition came from: `plugin`, `sdk`, `user`, `project`, …). Base trust decisions on `source`, not the name. `nil` for non-MCP tools (Claude Code v2.1.274+) |
16
17
  | `reason` | The reason the permission was denied |
17
18
 
18
19
  ## Hook State Helpers
@@ -35,3 +36,6 @@ Runs when a permission request is denied. Can request that Claude Code retries t
35
36
  ## Hook Exit Codes
36
37
 
37
38
  Exit code is ignored. Output is via `hookSpecificOutput.retry` (JSON API, exit 0 / stdout).
39
+
40
+ > [!NOTE]
41
+ > `retry: true` is ignored for "no-verdict" denials (where Claude Code produced no permission verdict to retry). `retry!` remains correct for the cases it applies to.
@@ -11,7 +11,8 @@ Input helpers to access the data provided by Claude Code through `STDIN`.
11
11
  |--------|-------------|
12
12
  | `tool_name` | Get the name of the tool requiring permission |
13
13
  | `tool_input` | Get the input data for the tool |
14
- | `tool_use_id` | Get the unique identifier for this tool use |
14
+ | `tool_use_id` | **Deprecated** — Claude Code doesn't send `tool_use_id` to PermissionRequest hooks, so this is always `nil`. Kept for backward compatibility |
15
+ | `mcp_server` | For MCP tools, a hash with the server's `name` and `source` (where its definition came from: `plugin`, `sdk`, `user`, `project`, …). Base trust decisions on `source`, not the name. `nil` for non-MCP tools (Claude Code v2.1.274+) |
15
16
 
16
17
  ## Hook State Helpers
17
18
  Hook state methods are helpers to modify the hook's internal state (`output_data`) before yielding back to Claude Code.
@@ -49,7 +50,7 @@ PermissionRequest hooks use the JSON API with exit code 0 for all permission dec
49
50
  |-----------|----------|
50
51
  | `exit 0` | Permission decision processed<br/>`STDOUT` contains JSON with decision |
51
52
  | `exit 1` | Non-blocking error<br/>`STDERR` shown to user |
52
- | `exit 2` | **Not recommended for PermissionRequest**<br/>Use JSON API with exit 0 instead |
53
+ | `exit 2` | **No longer honored for PermissionRequest** (Claude Code no longer treats it as a denial)<br/>Use the JSON API with exit 0 and a `decision.behavior` object instead |
53
54
 
54
55
  ## Example: Basic Permission Guard
55
56
 
@@ -63,7 +64,6 @@ class PermissionGuard < ClaudeHooks::PermissionRequest
63
64
 
64
65
  def call
65
66
  log "Permission requested for: #{tool_name}"
66
- log "Tool use ID: #{tool_use_id}"
67
67
  log "Permission mode: #{permission_mode}"
68
68
 
69
69
  # Auto-allow safe tools
@@ -0,0 +1,53 @@
1
+ # PostModelSwitch API
2
+
3
+ Available when inheriting from `ClaudeHooks::PostModelSwitch`:
4
+
5
+ Runs after the session's model changes (Claude Code v2.1.251+), including changes Claude Code makes on its own (automatic fallback, restoring the model on resume). Use it to give Claude model-specific guidance. It **can't block** — the model has already changed. Its `STDOUT` is added as context for Claude.
6
+
7
+ ## Input Helpers
8
+ Input helpers to access the data provided by Claude Code through `STDIN`.
9
+
10
+ [📚 Shared input helpers](COMMON.md#input-helpers)
11
+
12
+ `PostModelSwitch` receives the same fields as [PreModelSwitch](PRE_MODEL_SWITCH.md#input-helpers), with two extra `source` values: `'auto'` (a change Claude Code made on its own) and `'resume'` (the model restored when resuming a session). `requested_model` is `nil` when `source` is `'auto'`.
13
+
14
+ | Method | Description |
15
+ |--------|-------------|
16
+ | `from_model` | Model ID the switch changes from |
17
+ | `to_model` | Model ID the session switched to |
18
+ | `requested_model` | The model the request named (`nil` for `'auto'`; the restored setting for `'resume'`) |
19
+ | `source` | `'command'`, `'picker'`, `'sdk'`, `'auto'`, or `'resume'` |
20
+ | `context_tokens` | Tokens the next request re-sends as its prompt |
21
+ | `prompt_cache_warm` | Whether the prompt cache is likely still warm |
22
+ | `cache_ttl` | Prompt cache lifetime: `'5m'` or `'1h'` |
23
+ | `estimated_cache_write_usd` | Estimated cost (USD) of re-caching `context_tokens` |
24
+ | `pricing` | How `estimated_cache_write_usd` was priced |
25
+
26
+ ## Hook State Helpers
27
+ Hook state methods are helpers to modify the hook's internal state (`output_data`) before yielding back to Claude Code.
28
+
29
+ [📚 Shared hook state methods](COMMON.md#hook-state-methods)
30
+
31
+ | Method | Description |
32
+ |--------|-------------|
33
+ | `add_additional_context!(context)` | Add context for Claude after the model changes |
34
+ | `add_context!(context)` | Alias for `add_additional_context!` |
35
+
36
+ ## Output Helpers
37
+ Output helpers provide access to the hook's output data and helper methods for working with the output state.
38
+
39
+ [📚 Shared output helpers](COMMON.md#output-helpers)
40
+
41
+ | Method | Description |
42
+ |--------|-------------|
43
+ | `output.additional_context` | Get the additional context that was added |
44
+
45
+ ## Hook Exit Codes
46
+
47
+ Context-only — `PostModelSwitch` can't block.
48
+
49
+ | Exit Code | Behavior |
50
+ |-----------|----------|
51
+ | `exit 0` | Operation continues<br/>**`STDOUT` added as context to Claude** |
52
+ | `exit 1` | Non-blocking error<br/>`STDERR` shown to user |
53
+ | `exit 2` | N/A — rendered as a `<hook name> hook error` notice; the session proceeds |
@@ -13,6 +13,7 @@ Input helpers to access the data provided by Claude Code through `STDIN`.
13
13
  | `tool_input` | Get the input that was passed to the tool |
14
14
  | `tool_response` | Get the tool's response/output |
15
15
  | `tool_use_id` | Get the unique identifier for this tool use (e.g., `"toolu_01ABC123..."`) |
16
+ | `mcp_server` | For MCP tools, a hash with the server's `name` and `source` (where its definition came from: `plugin`, `sdk`, `user`, `project`, …). Base trust decisions on `source`, not the name. `nil` for non-MCP tools (Claude Code v2.1.274+) |
16
17
 
17
18
  ## Hook State Helpers
18
19
  Hook state methods are helpers to modify the hook's internal state (`output_data`) before yielding back to Claude Code.
@@ -24,6 +25,9 @@ Hook state methods are helpers to modify the hook's internal state (`output_data
24
25
  | `block_tool!(reason)` | Block the tool result from being used |
25
26
  | `approve_tool!(reason)` | Clear any previous block decision (default behavior) |
26
27
  | `add_additional_context!(context)` | Add context for Claude to consider after tool use |
28
+ | `update_tool_output!(value)` | Rewrite the tool output shown to Claude |
29
+ | `update_mcp_tool_output!(value)` | Rewrite an MCP tool's output |
30
+ | `classifier_context!(note)` | Add a short note about this call's result for the auto-mode classifier, not for Claude (Claude Code v2.1.236+) |
27
31
 
28
32
  ## Output Helpers
29
33
  Output helpers provide access to the hook's output data and helper methods for working with the output state.
@@ -36,6 +40,9 @@ Output helpers provide access to the hook's output data and helper methods for w
36
40
  | `output.reason` | Get the reason that was set for the decision |
37
41
  | `output.blocked?` | Check if the tool result has been blocked |
38
42
  | `output.additional_context` | Get the additional context that was added |
43
+ | `output.classifier_context` | Get the note sent to the auto-mode classifier |
44
+ | `output.updated_tool_output` | Get the rewritten tool output (if any) |
45
+ | `output.updated_mcp_tool_output` | Get the rewritten MCP tool output (if any) |
39
46
 
40
47
  ## Hook Exit Codes
41
48
 
@@ -13,6 +13,7 @@ Runs when a tool call fails. Non-blocking — output and exit code are ignored e
13
13
  | `tool_name` | The name of the tool that failed |
14
14
  | `tool_input` | The input data for the tool |
15
15
  | `tool_use_id` | The unique identifier for this tool use |
16
+ | `mcp_server` | For MCP tools, a hash with the server's `name` and `source` (where its definition came from: `plugin`, `sdk`, `user`, `project`, …). Base trust decisions on `source`, not the name. `nil` for non-MCP tools (Claude Code v2.1.274+) |
16
17
  | `error` | The error message |
17
18
  | `is_interrupt` | Whether the failure was caused by an interrupt (`true`/`false`) |
18
19
  | `interrupt?` | Alias for `is_interrupt` |
@@ -0,0 +1,67 @@
1
+ # PreModelSwitch API
2
+
3
+ Available when inheriting from `ClaudeHooks::PreModelSwitch`:
4
+
5
+ Runs before Claude Code applies a model switch that you or a client requested (Claude Code v2.1.251+). Use it to block a switch, require confirmation, or show what the switch will cost before it happens. Claude Code doesn't run this hook for switches it makes on its own (automatic fallback, resume) — those reach [PostModelSwitch](POST_MODEL_SWITCH.md) only.
6
+
7
+ ## Input Helpers
8
+ Input helpers to access the data provided by Claude Code through `STDIN`.
9
+
10
+ [📚 Shared input helpers](COMMON.md#input-helpers)
11
+
12
+ | Method | Description |
13
+ |--------|-------------|
14
+ | `from_model` | Model ID the switch changes from |
15
+ | `to_model` | Model ID the switch changes to (the matcher compares against its canonical name) |
16
+ | `requested_model` | The model the request named (an alias, a full ID, or `nil` for the default model) |
17
+ | `source` | Where the request came from: `'command'`, `'picker'`, or `'sdk'` |
18
+ | `context_tokens` | Tokens the next request re-sends as its prompt (`0` before the first response) |
19
+ | `prompt_cache_warm` | Whether the current model's prompt cache is likely still warm |
20
+ | `cache_ttl` | Prompt cache lifetime requested for this session: `'5m'` or `'1h'` |
21
+ | `estimated_cache_write_usd` | Estimated cost (USD) of re-caching `context_tokens` on `to_model` |
22
+ | `pricing` | How `estimated_cache_write_usd` was priced: `'configured'`, `'catalog'`, or `'default'` |
23
+
24
+ ## Hook State Helpers
25
+ Hook state methods are helpers to modify the hook's internal state (`output_data`) before yielding back to Claude Code.
26
+
27
+ [📚 Shared hook state methods](COMMON.md#hook-state-methods)
28
+
29
+ | Method | Description |
30
+ |--------|-------------|
31
+ | `approve!(reason)` / `allow!(reason)` | Let the switch proceed (skips the warm-cache confirmation) |
32
+ | `deny!(reason)` / `block_switch!(reason)` | Cancel the switch (`permissionDecision: 'deny'`) |
33
+ | `ask!(reason)` | Prompt the user to confirm the switch (`permissionDecision: 'ask'`) |
34
+ | `block!(reason)` | Cancel the switch via a top-level `decision: "block"` (equivalent to exit 2) |
35
+
36
+ `PreModelSwitch` does not accept `defer`, `updatedInput`, or `additionalContext`. Only `/model` in an interactive session can show the `'ask'` prompt; elsewhere `'ask'` is treated as a refusal.
37
+
38
+ ## Output Helpers
39
+ Output helpers provide access to the hook's output data and helper methods for working with the output state.
40
+
41
+ [📚 Shared output helpers](COMMON.md#output-helpers)
42
+
43
+ | Method | Description |
44
+ |--------|-------------|
45
+ | `output.allowed?` | Whether the switch was allowed (`permission_decision == 'allow'`) |
46
+ | `output.denied?` | Whether the switch was denied (`permission_decision == 'deny'`) |
47
+ | `output.blocked?` | Whether the switch was blocked (via `deny` **or** top-level `decision: "block"`) |
48
+ | `output.should_ask?` | Whether the user is asked to confirm (`permission_decision == 'ask'`) |
49
+ | `output.permission_decision` | The permission decision: `'allow'`, `'deny'`, or `'ask'` |
50
+ | `output.permission_reason` | The reason for the permission decision |
51
+ | `output.decision` | The top-level decision (`'block'` or `nil`) |
52
+ | `output.reason` | The top-level block reason |
53
+
54
+ When multiple hooks return different decisions, precedence is `deny > ask > allow`. `Output::PreModelSwitch.merge` follows the same order. If any hook calls `block!`, the merged `permissionDecision` becomes `deny`. If no hook set a `permissionDecision`, the merge leaves the field out rather than adding an `allow`.
55
+
56
+ ## Hook Exit Codes
57
+
58
+ `PreModelSwitch` uses the JSON API with exit code 0; the `permissionDecision` (or top-level `decision`) controls whether the switch proceeds.
59
+
60
+ | Exit Code | Behavior |
61
+ |-----------|----------|
62
+ | `exit 0` | Decision processed<br/>`STDOUT` contains JSON with the decision |
63
+ | `exit 1` | Non-blocking error<br/>`STDERR` shown to user; the switch applies |
64
+ | `exit 2` | **Cancels the switch**<br/>`STDERR` shown to user |
65
+
66
+ > [!NOTE]
67
+ > A `PreModelSwitch` hook that doesn't respond before its timeout **blocks** the switch (unlike `PreToolUse`). The default timeout for this event is 30 seconds.
@@ -12,6 +12,7 @@ Input helpers to access the data provided by Claude Code through `STDIN`.
12
12
  | `tool_name` | Get the name of the tool being used |
13
13
  | `tool_input` | Get the input data for the tool |
14
14
  | `tool_use_id` | Get the unique identifier for this tool use (e.g., `"toolu_01ABC123..."`) |
15
+ | `mcp_server` | For MCP tools, a hash with the server's `name` and `source` (where its definition came from: `plugin`, `sdk`, `user`, `project`, …). Base trust decisions on `source`, not the name. `nil` for non-MCP tools (Claude Code v2.1.274+) |
15
16
 
16
17
  ## Hook State Helpers
17
18
  Hook state methods are helpers to modify the hook's internal state (`output_data`) before yielding back to Claude Code.
@@ -9,7 +9,13 @@ Input helpers to access the data provided by Claude Code through `STDIN`.
9
9
 
10
10
  | Method | Description |
11
11
  |--------|-------------|
12
- | `source` | Get the session start source: `'startup'`, `'resume'`, `'clear'`, or `'compact'` |
12
+ | `source` | Get the session start source: `'startup'`, `'resume'`, `'clear'`, `'compact'`, or `'fork'` |
13
+ | `model` | The session model (Claude Code doesn't always include it) |
14
+ | `session_title` | The session title, when set |
15
+ | `seconds_since_last_response` | Wall-clock seconds since the last response (present on `resume`/`fork`) |
16
+ | `context_tokens` | Tokens the first request re-sends as its prompt (present on `resume`/`fork`) |
17
+ | `prompt_cache_likely_expired` | Whether the prompt cache is likely expired (present on `resume`/`fork`) |
18
+ | `estimated_cache_write_usd` | Estimated cost of re-caching `context_tokens` (present on `resume`/`fork`) |
13
19
 
14
20
  ## Hook State Helpers
15
21
  Hook state methods are helpers to modify the hook's internal state (`output_data`) before yielding back to Claude Code.
data/docs/API/SETUP.md CHANGED
@@ -18,8 +18,8 @@ Runs once during Claude Code startup (before any session begins). Use it to inje
18
18
 
19
19
  | Method | Description |
20
20
  |--------|-------------|
21
- | `add_additional_context!(context)` | Add contextual information for Claude |
22
- | `add_context!(context)` | Alias for `add_additional_context!` |
21
+ | `add_additional_context!(context)` | **Deprecated / no-op.** Claude Code now discards all `Setup` JSON output, including `additionalContext`. Use a `SessionStart` hook to inject context instead |
22
+ | `add_context!(context)` | Alias for `add_additional_context!` (also a no-op) |
23
23
 
24
24
  ## Output Helpers
25
25
 
@@ -33,5 +33,5 @@ Runs once during Claude Code startup (before any session begins). Use it to inje
33
33
 
34
34
  | Exit Code | Behavior |
35
35
  |-----------|----------|
36
- | `exit 0` | Continues normally; `STDOUT` added as context |
36
+ | `exit 0` | Continues normally. Claude Code discards all `Setup` output (including stdout context) |
37
37
  | `exit 2` | N/A — non-blocking event |
data/docs/API/STOP.md CHANGED
@@ -10,6 +10,9 @@ Input helpers to access the data provided by Claude Code through `STDIN`.
10
10
  | Method | Description |
11
11
  |--------|-------------|
12
12
  | `stop_hook_active` | Check if Claude Code is already continuing as a result of a stop hook |
13
+ | `last_assistant_message` | Text of Claude's final response for this turn |
14
+ | `background_tasks` | Background tasks still running (array) |
15
+ | `session_crons` | Scheduled session crons (array) |
13
16
 
14
17
  ## Hook State Helpers
15
18
  Hook state methods are helpers to modify the hook's internal state (`output_data`) before yielding back to Claude Code.
@@ -26,6 +29,9 @@ Hook state methods are helpers to modify the hook's internal state (`output_data
26
29
  | `block!(instructions)` | Alias for `continue_with_instructions!` |
27
30
  | `ensure_stopping!` | Allow Claude to stop normally (default behavior) |
28
31
 
32
+ > [!NOTE]
33
+ > Claude Code caps consecutive stop-hook continuations: after 8 in a row it lets the turn end despite the next block. The count resets each time Claude calls a tool. Raise the cap with the `CLAUDE_CODE_STOP_HOOK_BLOCK_CAP` environment variable. Check `stop_hook_active` to avoid loops.
34
+
29
35
  ## Output Helpers
30
36
  Output helpers provide access to the hook's output data and helper methods for working with the output state.
31
37
 
@@ -10,6 +10,13 @@ Input helpers to access the data provided by Claude Code through `STDIN`.
10
10
  | Method | Description |
11
11
  |--------|-------------|
12
12
  | `stop_hook_active` | Check if Claude Code is already continuing as a result of a stop hook |
13
+ | `last_assistant_message` | Text of the subagent's final response (with `SubagentHandback`, its closing text — not the delivered report) |
14
+ | `background_tasks` | Background tasks still running (array, scoped to the parent session) |
15
+ | `session_crons` | Scheduled session crons (array, scoped to the parent session) |
16
+ | `agent_transcript_path` | Path to the subagent's own transcript |
17
+
18
+ > [!NOTE]
19
+ > SubagentStop also fires when Claude Code's own internal agents finish (e.g. prompt suggestions, `/btw` side questions). For those, `agent_type` is the agent the session itself runs as, so don't assume every event is a subagent Claude spawned.
13
20
 
14
21
  ## Hook State Helpers
15
22
  Hook state methods are helpers to modify the hook's internal state (`output_data`) before yielding back to Claude Code.
@@ -26,6 +33,9 @@ Hook state methods are helpers to modify the hook's internal state (`output_data
26
33
  | `block!(instructions)` | Alias for `continue_with_instructions!` |
27
34
  | `ensure_stopping!` | Allow Claude to stop normally (default behavior) |
28
35
 
36
+ > [!NOTE]
37
+ > Claude Code caps consecutive stop-hook continuations: after 8 in a row it lets the turn end despite the next block. The count resets each time Claude calls a tool. Raise the cap with the `CLAUDE_CODE_STOP_HOOK_BLOCK_CAP` environment variable. Check `stop_hook_active` to avoid loops.
38
+
29
39
  ## Output Helpers
30
40
  Output helpers provide access to the hook's output data and helper methods for working with the output state.
31
41
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Available when inheriting from `ClaudeHooks::TaskCreated`:
4
4
 
5
- Runs when a teammate task is created. Can block Claude from continuing via `prevent_continue!` or exit 2.
5
+ Runs when a teammate task is created. Block task creation via `block!` (top-level `decision: "block"`) or exit 2. Claude Code **ignores** `continue: false` for this event, so `prevent_continue!` alone no longer blocks unless it results in exit 2.
6
6
 
7
7
  ## Input Helpers
8
8
 
@@ -22,9 +22,10 @@ Runs when a teammate task is created. Can block Claude from continuing via `prev
22
22
 
23
23
  | Method | Description |
24
24
  |--------|-------------|
25
- | `prevent_continue!(reason)` | Block Claude from continuing (`continue: false` + `stopReason`) |
25
+ | `block!(reason)` | Block task creation via top-level `decision: "block"`; `reason` is returned to Claude as the task-creation error |
26
+ | `allow!` / `unblock!` | Clear a previously set block |
26
27
 
27
- There is no top-level `decision` field for this event — block via `prevent_continue!` or exit 2.
28
+ Block via `block!` (top-level `decision`) or exit 2. `continue: false` is ignored for this event.
28
29
 
29
30
  ## Output Helpers
30
31
 
@@ -32,12 +33,13 @@ There is no top-level `decision` field for this event — block via `prevent_con
32
33
 
33
34
  | Method | Description |
34
35
  |--------|-------------|
35
- | `output.continue?` | Whether Claude will continue |
36
- | `output.stop_reason` | The reason Claude was stopped |
36
+ | `output.decision` | The decision (`'block'` or `nil`) |
37
+ | `output.reason` | The block reason returned to Claude |
38
+ | `output.blocked?` | Whether task creation was blocked (`decision == 'block'`) |
37
39
 
38
40
  ## Hook Exit Codes
39
41
 
40
42
  | Exit Code | Behavior |
41
43
  |-----------|----------|
42
- | `exit 0` | Claude continues |
43
- | `exit 2` | Blocks Claude (`continue: false`) |
44
+ | `exit 0` | Task creation proceeds (unless `decision: "block"` is set) |
45
+ | `exit 2` | Blocks task creation |
@@ -12,6 +12,13 @@ Input helpers to access the data provided by Claude Code through `STDIN`.
12
12
  | `prompt` | Get the user's prompt text |
13
13
  | `user_prompt` | Alias for `prompt` |
14
14
  | `current_prompt` | Alias for `prompt` |
15
+ | `session_title` | The session's custom title, when one is set (same meaning as SessionStart's `session_title`) |
16
+
17
+ > [!NOTE]
18
+ > `UserPromptSubmit` also fires on turns Claude Code starts on its own — a scheduled task or `/loop` iteration, a background subagent reporting back, or a message another session sends — so `prompt` isn't always text the user typed.
19
+
20
+ > [!NOTE]
21
+ > Pasted content that collapsed to a `[Pasted text #N]` placeholder arrives expanded in `prompt`. When Claude Code marks pasted text for Claude, it sits between `<pasted_content id="…">` and `</pasted_content id="…">` lines — account for them if you parse the prompt.
15
22
 
16
23
  ## Hook State Helpers
17
24
  Hook state methods are helpers to modify the hook's internal state (`output_data`) before yielding back to Claude Code.
@@ -25,6 +32,11 @@ Hook state methods are helpers to modify the hook's internal state (`output_data
25
32
  | `empty_additional_context!` | Remove additional context |
26
33
  | `block_prompt!(reason)` | Block the prompt from processing |
27
34
  | `unblock_prompt!` | Unblock a previously blocked prompt |
35
+ | `session_title!(title)` | Set the session title (e.g. name sessions from the prompt content) |
36
+ | `suppress_original_prompt!(value = true)` | When the prompt is blocked (`block_prompt!` or exit 2), leave the prompt text out of the block message |
37
+
38
+ > [!WARNING]
39
+ > By default the block message ends with `Original prompt:` and the submitted text, and is written to the session transcript on disk. `suppress_original_prompt!` removes it from that message only — the text can still appear in other local files such as prompt history, so a blocking hook isn't a way to keep a secret off disk.
28
40
 
29
41
  ## Output Helpers
30
42
  Output helpers provide access to the hook's output data and helper methods for working with the output state.
@@ -37,6 +49,8 @@ Output helpers provide access to the hook's output data and helper methods for w
37
49
  | `output.reason` | Get the reason for the decision |
38
50
  | `output.blocked?` | Check if the prompt has been blocked (decision == 'block') |
39
51
  | `output.additional_context` | Get the additional context that was added |
52
+ | `output.session_title` | Get the session title that was set (if any) |
53
+ | `output.suppress_original_prompt?` | Whether the prompt text is left out of the block message |
40
54
 
41
55
  ## Hook Exit Codes
42
56
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Available when inheriting from `ClaudeHooks::WorktreeRemove`:
4
4
 
5
- Runs when a git worktree is removed. Non-blocking — only `suppressOutput` is honored.
5
+ Runs when a worktree is removed. Pair it with a `WorktreeCreate` hook to control cleanup of the worktrees that hook creates. For those worktrees, **the hook does the cleanup**: Claude Code reads only its exit code and doesn't fall back to `git worktree remove` (it only does that when no `WorktreeRemove` hook is configured). Claude Code discards this hook's JSON output (`systemMessage`, `continue`, …); only the exit code matters, so `prevent_continue!` is what produces the non-zero exit. Claude Code never deletes a branch belonging to a hook-created worktree — if your `WorktreeCreate` hook created a branch, delete it here.
6
6
 
7
7
  ## Input Helpers
8
8
 
@@ -18,7 +18,7 @@ Runs when a git worktree is removed. Non-blocking — only `suppressOutput` is h
18
18
 
19
19
  | Method | Description |
20
20
  |--------|-------------|
21
- | `suppress_output!` | Hide this hook's `STDOUT` from transcript mode (the only honored output control) |
21
+ | `prevent_continue!(reason)` | Report that removal failed (results in exit 2) |
22
22
 
23
23
  ## Output Helpers
24
24
 
@@ -26,8 +26,13 @@ Runs when a git worktree is removed. Non-blocking — only `suppressOutput` is h
26
26
 
27
27
  | Method | Description |
28
28
  |--------|-------------|
29
- | `output.suppress_output?` | Whether output is suppressed |
29
+ | `output.continue?` | `false` when the hook reports failure (exit 2) |
30
+ | `output.stop_reason` | The failure reason |
30
31
 
31
32
  ## Hook Exit Codes
32
33
 
33
- Non-blocking. Exit code is ignored; only `suppressOutput` affects behavior.
34
+ | Exit Code | Behavior |
35
+ |-----------|----------|
36
+ | `exit 0` | The worktree **counts as removed** — make sure your hook deleted the directory |
37
+ | `exit 2` | **The removal fails** if the directory at `worktree_path` still exists; it stays on disk with no git fallback. A hook that deleted the directory before exiting non-zero still counts as removed |
38
+ | `exit 1` | Same as `exit 2` — Claude Code fails the removal on **any** non-zero exit. This includes a hook that raises: `CLI.run_hook` exits 1 on an error (even with the default `on_error: :allow`), so a crashing `WorktreeRemove` hook leaves the worktree on disk |