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.
- checksums.yaml +4 -4
- data/.claude/auto-fix.md +7 -0
- data/CHANGELOG.md +99 -4
- data/README.md +91 -15
- data/docs/{OUTPUT_MIGRATION_GUIDE.md → 1.0.0_MIGRATION_GUIDE.md} +17 -17
- data/docs/1.1.0_UPGRADE_GUIDE.md +269 -0
- data/docs/API/COMMON.md +1 -0
- data/docs/API/NOTIFICATION.md +1 -0
- data/docs/API/PERMISSION_REQUEST.md +196 -0
- data/docs/API/POST_TOOL_USE.md +1 -0
- data/docs/API/PRE_TOOL_USE.md +4 -0
- data/docs/API/SESSION_START.md +1 -1
- data/docs/WHY.md +15 -8
- data/docs/external/claude-hooks-reference.md +1310 -0
- data/example_dotclaude/hooks/entrypoints/pre_tool_use.rb +25 -0
- data/example_dotclaude/hooks/handlers/pre_tool_use/github_guard.rb +253 -0
- data/example_dotclaude/hooks/handlers/user_prompt_submit/append_rules.rb +2 -1
- data/example_dotclaude/plugins/README.md +175 -0
- data/example_dotclaude/settings.json +11 -0
- data/lib/claude_hooks/base.rb +5 -1
- data/lib/claude_hooks/notification.rb +5 -1
- data/lib/claude_hooks/output/base.rb +2 -0
- data/lib/claude_hooks/output/permission_request.rb +95 -0
- data/lib/claude_hooks/output/post_tool_use.rb +14 -6
- data/lib/claude_hooks/output/pre_tool_use.rb +47 -32
- data/lib/claude_hooks/output/stop.rb +13 -6
- data/lib/claude_hooks/output/user_prompt_submit.rb +14 -7
- data/lib/claude_hooks/permission_request.rb +56 -0
- data/lib/claude_hooks/post_tool_use.rb +5 -1
- data/lib/claude_hooks/pre_tool_use.rb +16 -1
- data/lib/claude_hooks/stop.rb +8 -0
- data/lib/claude_hooks/version.rb +1 -1
- data/lib/claude_hooks.rb +2 -0
- metadata +12 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d7cd63a0fbf165b912baa3fab487e056f05bc10d29b77123e2029ef1dce385a4
|
|
4
|
+
data.tar.gz: '054837c4d15be8b9d248aa81db5966d80c2c5e0f9a322c622bdf781bf8d0af20'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 38ef48d0b9cb24d30522f213302d5a3af48fca24ecd90af259b7d6a034fcbd8ddc706c0c2783db8b5372fb59c3d7a4429b7cc7523cd0299e7ed2a7b8cb8b023e
|
|
7
|
+
data.tar.gz: 82cdb4b713646492a92a1bae0143a7051fe2bc4f6afe0ad7394bb6f7d50704035a8ddd863bb1b4d12885867859cd6b5b743c618d2fc87682e26918150162b25a
|
data/.claude/auto-fix.md
ADDED
|
@@ -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
|
-
>
|
|
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
|
|
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
|
-
| **
|
|
388
|
-
| **
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
STDERR.puts JSON.generate({
|
|
81
|
+
continue: false,
|
|
82
|
+
stopReason: "Hook execution error: #{e.message}",
|
|
83
|
+
suppressOutput: false
|
|
84
84
|
})
|
|
85
|
-
|
|
85
|
+
# Non-blocking error
|
|
86
|
+
exit 1
|
|
86
87
|
end
|
|
87
88
|
```
|
|
88
89
|
|
|
89
90
|
**Benefits:**
|
|
90
|
-
- **
|
|
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
|
|
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 `
|
|
223
|
-
2. **Replace manual exit logic** with `hook.output_and_exit` or `
|
|
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.
|