claude_hooks 1.0.0 → 1.1.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/auto-fix.md +7 -0
  3. data/CHANGELOG.md +99 -4
  4. data/README.md +91 -15
  5. data/docs/{OUTPUT_MIGRATION_GUIDE.md → 1.0.0_MIGRATION_GUIDE.md} +17 -17
  6. data/docs/1.1.0_UPGRADE_GUIDE.md +269 -0
  7. data/docs/API/COMMON.md +1 -0
  8. data/docs/API/NOTIFICATION.md +1 -0
  9. data/docs/API/PERMISSION_REQUEST.md +196 -0
  10. data/docs/API/POST_TOOL_USE.md +1 -0
  11. data/docs/API/PRE_TOOL_USE.md +4 -0
  12. data/docs/API/SESSION_START.md +1 -1
  13. data/docs/WHY.md +15 -8
  14. data/docs/external/claude-hooks-reference.md +1310 -0
  15. data/example_dotclaude/hooks/entrypoints/pre_tool_use.rb +25 -0
  16. data/example_dotclaude/hooks/handlers/pre_tool_use/github_guard.rb +253 -0
  17. data/example_dotclaude/hooks/handlers/user_prompt_submit/append_rules.rb +2 -1
  18. data/example_dotclaude/plugins/README.md +175 -0
  19. data/example_dotclaude/settings.json +11 -0
  20. data/lib/claude_hooks/base.rb +5 -1
  21. data/lib/claude_hooks/notification.rb +5 -1
  22. data/lib/claude_hooks/output/base.rb +2 -0
  23. data/lib/claude_hooks/output/permission_request.rb +95 -0
  24. data/lib/claude_hooks/output/post_tool_use.rb +14 -6
  25. data/lib/claude_hooks/output/pre_tool_use.rb +47 -32
  26. data/lib/claude_hooks/output/stop.rb +13 -6
  27. data/lib/claude_hooks/output/user_prompt_submit.rb +14 -7
  28. data/lib/claude_hooks/permission_request.rb +56 -0
  29. data/lib/claude_hooks/post_tool_use.rb +5 -1
  30. data/lib/claude_hooks/pre_tool_use.rb +16 -1
  31. data/lib/claude_hooks/stop.rb +8 -0
  32. data/lib/claude_hooks/version.rb +1 -1
  33. data/lib/claude_hooks.rb +2 -0
  34. metadata +12 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbd6349008b693bddb770b79ac5883a746c826a800ed7141f4de466bd88025e0
4
- data.tar.gz: b7a724ed1655ecb7c4af3393619e5dd800b0b59a08721c6d4b7279c4d31a185d
3
+ metadata.gz: d7cd63a0fbf165b912baa3fab487e056f05bc10d29b77123e2029ef1dce385a4
4
+ data.tar.gz: '054837c4d15be8b9d248aa81db5966d80c2c5e0f9a322c622bdf781bf8d0af20'
5
5
  SHA512:
6
- metadata.gz: a1596119ed1ee346ed133a76a0991cc64f8be594905e96e6b056e6e5187be91ca43ffd4c28c3e822725b5ee4bdb0bdbc03aa0cea38c06a5813b94c3a381ff0c3
7
- data.tar.gz: 6063e35f39ffabd8eaf78e9deccd009e56f9103c86f69dedb6a2ed66b97cc9a8606d0aafb6ee2467d6fa29536031f4d0d671ff20e466c672009d16e2c2a962ea
6
+ metadata.gz: 38ef48d0b9cb24d30522f213302d5a3af48fca24ecd90af259b7d6a034fcbd8ddc706c0c2783db8b5372fb59c3d7a4429b7cc7523cd0299e7ed2a7b8cb8b023e
7
+ data.tar.gz: 82cdb4b713646492a92a1bae0143a7051fe2bc4f6afe0ad7394bb6f7d50704035a8ddd863bb1b4d12885867859cd6b5b743c618d2fc87682e26918150162b25a
@@ -0,0 +1,7 @@
1
+ Context: This gem is a wrapper around Claude Code Hooks system, providing a Ruby DSL to define hooks. The github repository has an automated system through github actions that automatically (weekly) creates a diff of the last known state of Claude Code's hook documentation vs the current (online) Claude Code's hooks documentation. It then generates a diff between those and create an issue with the diff.
2
+
3
+ Intended goal: Take all those issues and all those diffs and evaluate if the gem / code needs to be updated to match the changes made by anthropic to their Claude Code Hooks system.
4
+
5
+ First step: Retrieve all the issues at: https://github.com/gabriel-dehan/claude_hooks/issues
6
+ And assess which issues may implement things that need to be changed (a lot of issues contain useless diff, with wording changes, link changes, etc that are not relevant).
7
+ Second step: Take those relevant issues and look at the diff, then compare it with the current codebase / readme / documentation to see if there is a mismatch Third step: Create a plan to tackle those changes
data/CHANGELOG.md CHANGED
@@ -5,11 +5,106 @@ 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.1.0] - 2026-01-04
9
+
10
+ ### Added
11
+
12
+ - **New PermissionRequest hook type** - Handles permission request events from Claude Code
13
+ - New `ClaudeHooks::PermissionRequest` hook class
14
+ - New `ClaudeHooks::Output::PermissionRequest` output class
15
+ - Methods: `allow_permission!`, `deny_permission!`, `update_input_and_allow!`
16
+ - Semantic helpers: `allowed?`, `denied?`, `input_updated?`
17
+ - Exit code 0 with stdout (JSON API pattern)
18
+ - Merge logic: deny > allow (most restrictive wins)
19
+
20
+ - **permission_mode field** - Added to all hooks as common input field
21
+ - Values: `default`, `plan`, `acceptEdits`, `dontAsk`, `bypassPermissions`
22
+ - Accessible via `permission_mode` method on all hook instances
23
+ - Defaults to `'default'` when not provided
24
+ - Supports both snake_case (`permission_mode`) and camelCase (`permissionMode`)
25
+
26
+ - **tool_use_id field** - Added to PreToolUse and PostToolUse hooks
27
+ - Unique identifier for each tool use event (e.g., `"toolu_01ABC123..."`)
28
+ - Accessible via `tool_use_id` method
29
+ - Supports both snake_case and camelCase input
30
+
31
+ - **notification_type field** - Added to Notification hook
32
+ - Values: `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`
33
+ - Accessible via `notification_type` method
34
+ - Enables filtering notifications by type using matchers
35
+
36
+ - **updatedInput support** - Added to PreToolUse hook
37
+ - New method: `update_tool_input!(hash)` for modifying tool input before execution
38
+ - Output helpers: `input_updated?`, `updated_input`
39
+ - Automatically sets `permissionDecision` to `'allow'`
40
+ - Merge logic: last updatedInput wins (most recent transformation)
41
+ - Works seamlessly with existing approval/denial methods
42
+
43
+ ### Changed
44
+
45
+ - Updated all hook classes to include new input fields where applicable
46
+ - Enhanced PreToolUse output class with updatedInput merge logic
47
+ - Updated main module to require PermissionRequest classes
48
+ - Updated output factory to support PermissionRequest hook type
49
+
50
+ ### Notes
51
+
52
+ - **All changes are backward compatible** - Existing hooks continue to work without modification
53
+ - New fields are optional with sensible defaults
54
+ - No breaking changes to existing APIs
55
+ - Aligns with Anthropic's Claude Code Hooks documentation as of January 2026
56
+ - Total hook types: 10 (added PermissionRequest)
57
+
58
+ ## [1.0.2] - 2026-01-04
59
+
60
+ ### Fixed
61
+
62
+ - **Critical: Fixed hook output contract for JSON API** - Stop, PostToolUse, UserPromptSubmit, and PreToolUse hooks now correctly use stdout + exit 0 when outputting structured JSON
63
+ - Stop hooks now output to stdout with exit 0 instead of stderr with exit 2 (PR #15, fixes #11)
64
+ - PostToolUse hooks now use stdout + exit 0 for JSON API consistency
65
+ - UserPromptSubmit hooks now use stdout + exit 0 for JSON API consistency
66
+ - PreToolUse hooks now use exit 0 for all permission decisions (previously varied: 0 for allow, 1 for ask, 2 for deny)
67
+ - This aligns with Anthropic's official guidance: when using advanced JSON API with decision/reason fields, hooks must output to stdout with exit 0
68
+ - **Breaking behavior fixed**: Plugin hooks with `continue_with_instructions!` now work correctly
69
+ - Reference: https://github.com/anthropics/claude-code/issues/10875
70
+ - Reference: https://github.com/gabriel-dehan/claude_hooks/issues/11
71
+
72
+ ### Notes
73
+
74
+ - All tests updated and passing (147 runs, 262 assertions, 0 failures)
75
+ - This fix ensures compatibility with Claude Code's plugin system
76
+ - The exit code 2 + stderr approach is only for simple text output without JSON structure
77
+
78
+ ## [1.0.1] - 2025-10-13
79
+
80
+ ### Documentation
81
+
82
+ - **Added comprehensive plugin hooks documentation** - Full guide on using the Claude Hooks DSL with Claude Code plugins
83
+ - New "Plugin Hooks Support" section in README with working examples
84
+ - Created `example_dotclaude/plugins/README.md` with complete plugin development guide
85
+ - Example plugin formatter implementation using the DSL
86
+ - Environment variables documentation for plugin development
87
+ - Best practices and testing instructions for plugin hooks
88
+ - **Updated SessionStart hook documentation** - Added the new `compact` matcher introduced in official Claude Hooks documentation
89
+ - Updated `docs/API/SESSION_START.md` to include `'compact'` as a valid source value
90
+ - Updated SessionStart hook type description to mention compact functionality
91
+ - **Synchronized with official documentation** - All documentation now matches the official Claude Hooks reference as of 2025-10-13
92
+ - Plugin hooks feature (Issue #9)
93
+ - SessionStart `compact` matcher (Issue #2)
94
+
95
+ ### Notes
96
+
97
+ - No code changes required - the implementation already supports all documented features
98
+ - The DSL's dynamic implementation handles the new `compact` source automatically
99
+ - Plugin environment variables (`CLAUDE_PLUGIN_ROOT`) work seamlessly with existing configuration system
100
+
8
101
  ## [1.0.0] - 2025-08-27
9
102
 
10
103
  > [!WARNING]
11
104
  > These changes are not backward compatible (hence the version bump to 1.0.0), the API has changed too much.
12
105
 
106
+ Follow the [migration guide](docs/1.0.0_MIGRATION_GUIDE.md) to migrate to the new API.
107
+
13
108
  ### Changed
14
109
 
15
110
  #### Revamped documentation
@@ -57,13 +152,13 @@ begin
57
152
  input_data = JSON.parse(STDIN.read)
58
153
  hook = MyHook.new(input_data)
59
154
  result = hook.call
60
-
155
+
61
156
  # Manual stream and exit code selection
62
157
  if result['continue'] != false
63
158
  if result.dig('hookSpecificOutput', 'permissionDecision') == 'deny'
64
159
  STDERR.puts hook.stringify_output
65
160
  exit 1
66
- elsif result.dig('hookSpecificOutput', 'permissionDecision') == 'ask'
161
+ elsif result.dig('hookSpecificOutput', 'permissionDecision') == 'ask'
67
162
  STDERR.puts hook.stringify_output
68
163
  exit 2
69
164
  else
@@ -89,9 +184,9 @@ begin
89
184
  input_data = JSON.parse(STDIN.read)
90
185
  hook = MyHook.new(input_data)
91
186
  hook.call
92
-
187
+
93
188
  # Handles everything automatically!
94
- hook.output_and_exit
189
+ hook.output_and_exit
95
190
  rescue StandardError => e
96
191
  # Error handling...
97
192
  end
data/README.md CHANGED
@@ -1,11 +1,16 @@
1
1
  # Ruby DSL for Claude Code hooks
2
2
 
3
+ > [!IMPORTANT]
4
+ > v1.0.0 just released and is introducing breaking changes. Please read the [CHANGELOG](CHANGELOG.md) for more information.
5
+
6
+
3
7
  A Ruby DSL (Domain Specific Language) for creating Claude Code hooks. This will hopefully make creating and configuring new hooks way easier.
4
8
 
5
9
  [**Why use this instead of writing bash, or simple ruby scripts?**](docs/WHY.md)
6
10
 
7
11
  > You might also be interested in my other project, a [Claude Code statusline](https://github.com/gabriel-dehan/claude_monitor_statusline) that shows your Claude usage in realtime, inside Claude Code ✨.
8
12
 
13
+
9
14
  ## 📖 Table of Contents
10
15
 
11
16
  - [Ruby DSL for Claude Code hooks](#ruby-dsl-for-claude-code-hooks)
@@ -18,6 +23,7 @@ A Ruby DSL (Domain Specific Language) for creating Claude Code hooks. This will
18
23
  - [📚 API Reference](#-api-reference)
19
24
  - [📝 Example: Tool usage monitor](#-example-tool-usage-monitor)
20
25
  - [🔄 Hook Output](#-hook-output)
26
+ - [🔌 Plugin Hooks Support](#-plugin-hooks-support)
21
27
  - [🚨 Advices](#-advices)
22
28
  - [⚠️ Troubleshooting](#️-troubleshooting)
23
29
  - [🧪 CLI Debugging](#-cli-debugging)
@@ -27,7 +33,7 @@ A Ruby DSL (Domain Specific Language) for creating Claude Code hooks. This will
27
33
  ## 🚀 Quick Start
28
34
 
29
35
  > [!TIP]
30
- > An example is available in [`example_dotclaude/hooks/`](example_dotclaude/hooks/)
36
+ > Examples are available in [`example_dotclaude/hooks/`](example_dotclaude/hooks/). The GithubGuard in particular is a good example of a solid hook. You can also check [Kyle's hooks for some great examples](https://github.com/kylesnowschwartz/dotfiles/blob/main/claude/hooks)
31
37
 
32
38
  Here's how to create a simple hook:
33
39
 
@@ -58,7 +64,7 @@ if __FILE__ == $0
58
64
 
59
65
  hook = AddContextAfterPrompt.new(input_data)
60
66
  hook.call
61
-
67
+
62
68
  # Handles output and exit code depending on the hook state.
63
69
  # In this case, uses exit code 0 (success) and prints output to STDOUT
64
70
  hook.output_and_exit
@@ -255,10 +261,11 @@ The framework supports the following hook types:
255
261
 
256
262
  | Hook Type | Class | Description |
257
263
  |-----------|-------|-------------|
258
- | **[SessionStart](docs/API/SESSION_START.md)** | `ClaudeHooks::SessionStart` | Hooks that run when Claude Code starts a new session or resumes |
264
+ | **[SessionStart](docs/API/SESSION_START.md)** | `ClaudeHooks::SessionStart` | Hooks that run when Claude Code starts a new session, resumes, or compacts |
259
265
  | **[UserPromptSubmit](docs/API/USER_PROMPT_SUBMIT.md)** | `ClaudeHooks::UserPromptSubmit` | Hooks that run before the user's prompt is processed |
260
266
  | **[Notification](docs/API/NOTIFICATION.md)** | `ClaudeHooks::Notification` | Hooks that run when Claude Code sends notifications |
261
267
  | **[PreToolUse](docs/API/PRE_TOOL_USE.md)** | `ClaudeHooks::PreToolUse` | Hooks that run before a tool is used |
268
+ | **[PermissionRequest](docs/API/PERMISSION_REQUEST.md)** | `ClaudeHooks::PermissionRequest` | Hooks that run when Claude requests permission |
262
269
  | **[PostToolUse](docs/API/POST_TOOL_USE.md)** | `ClaudeHooks::PostToolUse` | Hooks that run after a tool is used |
263
270
  | **[Stop](docs/API/STOP.md)** | `ClaudeHooks::Stop` | Hooks that run when Claude Code finishes responding |
264
271
  | **[SubagentStop](docs/API/SUBAGENT_STOP.md)** | `ClaudeHooks::SubagentStop` | Hooks that run when subagent tasks complete |
@@ -269,7 +276,7 @@ The framework supports the following hook types:
269
276
 
270
277
  ### A very simplified view of how a hook works in Claude Code
271
278
 
272
- Claude Code hooks in essence work in a very simple way:
279
+ Claude Code hooks in essence work in a very simple way:
273
280
  - Claude Code passes data to the hook script through `STDIN`
274
281
  - The hook uses the data to do its thing
275
282
  - The hook outputs data to `STDOUT` or `STDERR` and then `exit`s with the proper code:
@@ -342,7 +349,7 @@ class AddContextAfterPrompt < ClaudeHooks::UserPromptSubmit
342
349
  log "Prompt blocked: #{current_prompt} because of bad word"
343
350
  end
344
351
 
345
- # Return output if you need it
352
+ # Return output if you need it
346
353
  output
347
354
  end
348
355
  end
@@ -355,7 +362,7 @@ if __FILE__ == $0
355
362
  hook = AddContextAfterPrompt.new(input_data)
356
363
  # Call the hook
357
364
  hook.call
358
-
365
+
359
366
  # Uses exit code 0 (success) and outputs to STDIN if the prompt wasn't blocked
360
367
  # Uses exit code 2 (blocking error) and outputs to STDERR if the prompt was blocked
361
368
  hook.output_and_exit
@@ -381,11 +388,12 @@ The framework supports all existing hook types with their respective input field
381
388
 
382
389
  | Hook Type | Input Fields |
383
390
  |-----------|--------------|
384
- | **Common** | `session_id`, `transcript_path`, `cwd`, `hook_event_name` |
391
+ | **Common** | `session_id`, `transcript_path`, `cwd`, `hook_event_name`, `permission_mode` |
385
392
  | **UserPromptSubmit** | `prompt` |
386
- | **PreToolUse** | `tool_name`, `tool_input` |
387
- | **PostToolUse** | `tool_name`, `tool_input`, `tool_response` |
388
- | **Notification** | `message` |
393
+ | **PreToolUse** | `tool_name`, `tool_input`, `tool_use_id` |
394
+ | **PermissionRequest** | `tool_name`, `tool_input`, `tool_use_id` |
395
+ | **PostToolUse** | `tool_name`, `tool_input`, `tool_response`, `tool_use_id` |
396
+ | **Notification** | `message`, `notification_type` |
389
397
  | **Stop** | `stop_hook_active` |
390
398
  | **SubagentStop** | `stop_hook_active` |
391
399
  | **PreCompact** | `trigger`, `custom_instructions` |
@@ -401,6 +409,7 @@ The framework supports all existing hook types with their respective input field
401
409
  - [🚀 Session Start Hooks](docs/API/SESSION_START.md)
402
410
  - [🖋️ User Prompt Submit Hooks](docs/API/USER_PROMPT_SUBMIT.md)
403
411
  - [🛠️ Pre-Tool Use Hooks](docs/API/PRE_TOOL_USE.md)
412
+ - [🔐 Permission Request Hooks](docs/API/PERMISSION_REQUEST.md)
404
413
  - [🔧 Post-Tool Use Hooks](docs/API/POST_TOOL_USE.md)
405
414
  - [📝 Pre-Compact Hooks](docs/API/PRE_COMPACT.md)
406
415
  - [⏹️ Stop Hooks](docs/API/STOP.md)
@@ -535,9 +544,9 @@ end
535
544
 
536
545
  Hooks provide access to their output (which acts as the "state" of a hook) through the `output` method.
537
546
 
538
- This method will return an output object based on the hook's type class (e.g: `ClaudeHooks::Output::UserPromptSubmit`) that provides helper methods:
547
+ This method will return an output object based on the hook's type class (e.g: `ClaudeHooks::Output::UserPromptSubmit`) that provides helper methods:
539
548
  - to access output data
540
- - for merging multiple outputs
549
+ - for merging multiple outputs
541
550
  - for sending the right exit codes and output data back to Claude Code through the proper stream.
542
551
 
543
552
  > [!TIP]
@@ -547,7 +556,7 @@ This method will return an output object based on the hook's type class (e.g: `C
547
556
  ### 🔄 Hook Output Merging
548
557
 
549
558
  Often, you will want to call multiple hooks from a same entrypoint.
550
- Each hook type's `output` provides a `merge` method that will try to intelligently merge multiple hook results.
559
+ Each hook type's `output` provides a `merge` method that will try to intelligently merge multiple hook results.
551
560
  Merged outputs always inherit the **most restrictive behavior**.
552
561
 
553
562
  ```ruby
@@ -579,12 +588,12 @@ begin
579
588
  # - additionalContext: concatenated
580
589
  merged_output = ClaudeHooks::Output::UserPromptSubmit.merge(
581
590
  hook1.output,
582
- hook2.output,
591
+ hook2.output,
583
592
  hook3.output
584
593
  )
585
594
 
586
595
  # Automatically handles outputting to the right stream (STDOUT or STDERR) and uses the right exit code depending on hook state
587
- merged_output.output_and_exit
596
+ merged_output.output_and_exit
588
597
  end
589
598
  ```
590
599
 
@@ -648,6 +657,73 @@ exit 2
648
657
  > [!WARNING]
649
658
  > You don't have to manually do this, just use `output_and_exit` to automatically handle this.
650
659
 
660
+ ## 🔌 Plugin Hooks Support
661
+
662
+ This DSL works seamlessly with [Claude Code plugins](https://docs.claude.com/en/docs/claude-code/plugins)! When creating plugin hooks, you can use the exact same Ruby DSL and enjoy all the same benefits.
663
+
664
+ **How plugin hooks work:**
665
+ - Plugin hooks are defined in the plugin's `hooks/hooks.json` file
666
+ - They use the `${CLAUDE_PLUGIN_ROOT}` environment variable to reference plugin files
667
+ - Plugin hooks are automatically merged with user and project hooks when plugins are enabled
668
+ - Multiple hooks from different sources can respond to the same event
669
+
670
+ **Example plugin hook configuration (`hooks/hooks.json`):**
671
+ ```json
672
+ {
673
+ "description": "Automatic code formatting",
674
+ "hooks": {
675
+ "PostToolUse": [
676
+ {
677
+ "matcher": "Write|Edit",
678
+ "hooks": [
679
+ {
680
+ "type": "command",
681
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/formatter.rb",
682
+ "timeout": 30
683
+ }
684
+ ]
685
+ }
686
+ ]
687
+ }
688
+ }
689
+ ```
690
+
691
+ **Using this DSL in your plugin hooks (`hooks/scripts/formatter.rb`):**
692
+ ```ruby
693
+ #!/usr/bin/env ruby
694
+ require 'claude_hooks'
695
+
696
+ class PluginFormatter < ClaudeHooks::PostToolUse
697
+ def call
698
+ log "Plugin executing from: #{ENV['CLAUDE_PLUGIN_ROOT']}"
699
+
700
+ if tool_name.match?(/Write|Edit/)
701
+ file_path = tool_input['file_path']
702
+ log "Formatting file: #{file_path}"
703
+
704
+ # Your formatting logic here
705
+ # Can use all the DSL helper methods!
706
+ end
707
+
708
+ output
709
+ end
710
+ end
711
+
712
+ if __FILE__ == $0
713
+ input_data = JSON.parse(STDIN.read)
714
+ hook = PluginFormatter.new(input_data)
715
+ hook.call
716
+ hook.output_and_exit
717
+ end
718
+ ```
719
+
720
+ **Environment variables available in plugins:**
721
+ - `${CLAUDE_PLUGIN_ROOT}`: Absolute path to the plugin directory
722
+ - `${CLAUDE_PROJECT_DIR}`: Project root directory (same as for project hooks)
723
+ - All standard environment variables and configuration options work the same way
724
+
725
+ See the [plugin components reference](https://docs.claude.com/en/docs/claude-code/plugins-reference#hooks) for more details on creating plugin hooks.
726
+
651
727
  ## 🚨 Advices
652
728
 
653
729
  1. **Logging**: Use `log()` method instead of `puts` to avoid interfering with Claude Code's expected output.
@@ -1,6 +1,6 @@
1
- # Migration Guide: Using Output Objects
1
+ # Migration Guide: 1.0.0
2
2
 
3
- This guide shows how to migrate from the old manual exit code patterns to the new simplified output object system.
3
+ This guide shows how to migrate from the old manual exit code patterns to the new simplified way introduced in v1.0.0.
4
4
 
5
5
  ## Problem: The Old Way (Before)
6
6
 
@@ -46,12 +46,12 @@ rescue StandardError => e
46
46
  stopReason: "PreToolUser Hook execution error: #{e.message}",
47
47
  suppressOutput: false
48
48
  })
49
- exit 0 # Allow anyway to not block developers if there is an issue
49
+ exit 1 # Allow anyway to not block developers if there is an issue
50
50
  end
51
51
  ```
52
52
 
53
53
  **Problems with this approach:**
54
- - 30+ lines of repetitive boilerplate
54
+ - A lot of lines of repetitive boilerplate
55
55
  - Need to understand Claude Code's internal exit code semantics
56
56
  - Manual stream selection (STDOUT vs STDERR)
57
57
  - Error-prone - easy to get exit codes wrong
@@ -77,20 +77,21 @@ begin
77
77
 
78
78
  rescue StandardError => e
79
79
  # NEW: Simple error handling
80
- error_output = ClaudeHooks::Output::PreToolUse.new({
81
- 'continue' => false,
82
- 'stopReason' => "Hook execution error: #{e.message}",
83
- 'suppressOutput' => false
80
+ STDERR.puts JSON.generate({
81
+ continue: false,
82
+ stopReason: "Hook execution error: #{e.message}",
83
+ suppressOutput: false
84
84
  })
85
- error_output.output_and_exit # Automatically uses STDERR and exit 1
85
+ # Non-blocking error
86
+ exit 1
86
87
  end
87
88
  ```
88
89
 
89
90
  **Benefits:**
90
- - **10 lines instead of 30+** - 70% less code
91
+ - **More concise+** - 70% less boilerplate code
91
92
  - **No Claude Code knowledge needed** - just use the gem's API
92
93
  - **Automatic exit codes** based on hook-specific logic
93
- - **Automatic stream selection** (STDOUT/STDERR)
94
+ - **Automatic stream selection** (STDOUT/STDERR). Note: `PreToolUse` always outputs to STDOUT.
94
95
  - **Type-safe access** to hook-specific fields
95
96
 
96
97
  ## New Output Object API
@@ -143,7 +144,7 @@ output = hook.output
143
144
  puts "Should continue? #{output.should_continue?}" # true if decision == 'block'
144
145
  puts "Continue instructions: #{output.continue_instructions}"
145
146
 
146
- hook.output_and_exit # exit 1 for "continue", exit 0 for "stop"
147
+ hook.output_and_exit # exit 2 for "continue", exit 0 for "stop"
147
148
  ```
148
149
 
149
150
  ## Merging Multiple Hooks
@@ -213,16 +214,15 @@ end
213
214
  - `ClaudeHooks::Output::SubagentStop`
214
215
  - `ClaudeHooks::Output::Notification`
215
216
  - `ClaudeHooks::Output::SessionStart`
217
+ - `ClaudeHooks::Output::SessionEnd`
216
218
  - `ClaudeHooks::Output::PreCompact`
217
219
 
218
220
  Each handles the specific exit code logic and semantic helpers for its hook type.
219
221
 
220
222
  ## Migration Steps
221
223
 
222
- 1. **Update your hook handlers** to return `output_data` (most already do)
223
- 2. **Replace manual exit logic** with `hook.output_and_exit` or `output.output_and_exit`
224
+ 1. **Update your hook handlers** to return ``output` instead of `output_data`
225
+ 2. **Replace manual exit logic** with `hook.output_and_exit` or `merged_output.output_and_exit`
224
226
  3. **Use semantic helpers** instead of digging through hash structures
225
- 4. **Use output object merging** instead of class methods
226
- 5. **Enjoy the simplified, cleaner code!**
227
+ 4. **Use output object classes for merging** instead of hook class methods
227
228
 
228
- The old patterns still work for backward compatibility, but the new output objects make everything much cleaner and less error-prone.