claude_hooks 1.0.2 → 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 +50 -0
- data/README.md +7 -4
- 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/external/claude-hooks-reference.md +1294 -18
- 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/pre_tool_use.rb +18 -0
- 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/version.rb +1 -1
- data/lib/claude_hooks.rb +2 -0
- metadata +6 -1
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,6 +5,56 @@ 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
|
+
|
|
8
58
|
## [1.0.2] - 2026-01-04
|
|
9
59
|
|
|
10
60
|
### Fixed
|
data/README.md
CHANGED
|
@@ -265,6 +265,7 @@ The framework supports the following hook types:
|
|
|
265
265
|
| **[UserPromptSubmit](docs/API/USER_PROMPT_SUBMIT.md)** | `ClaudeHooks::UserPromptSubmit` | Hooks that run before the user's prompt is processed |
|
|
266
266
|
| **[Notification](docs/API/NOTIFICATION.md)** | `ClaudeHooks::Notification` | Hooks that run when Claude Code sends notifications |
|
|
267
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 |
|
|
268
269
|
| **[PostToolUse](docs/API/POST_TOOL_USE.md)** | `ClaudeHooks::PostToolUse` | Hooks that run after a tool is used |
|
|
269
270
|
| **[Stop](docs/API/STOP.md)** | `ClaudeHooks::Stop` | Hooks that run when Claude Code finishes responding |
|
|
270
271
|
| **[SubagentStop](docs/API/SUBAGENT_STOP.md)** | `ClaudeHooks::SubagentStop` | Hooks that run when subagent tasks complete |
|
|
@@ -387,11 +388,12 @@ The framework supports all existing hook types with their respective input field
|
|
|
387
388
|
|
|
388
389
|
| Hook Type | Input Fields |
|
|
389
390
|
|-----------|--------------|
|
|
390
|
-
| **Common** | `session_id`, `transcript_path`, `cwd`, `hook_event_name` |
|
|
391
|
+
| **Common** | `session_id`, `transcript_path`, `cwd`, `hook_event_name`, `permission_mode` |
|
|
391
392
|
| **UserPromptSubmit** | `prompt` |
|
|
392
|
-
| **PreToolUse** | `tool_name`, `tool_input` |
|
|
393
|
-
| **
|
|
394
|
-
| **
|
|
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` |
|
|
395
397
|
| **Stop** | `stop_hook_active` |
|
|
396
398
|
| **SubagentStop** | `stop_hook_active` |
|
|
397
399
|
| **PreCompact** | `trigger`, `custom_instructions` |
|
|
@@ -407,6 +409,7 @@ The framework supports all existing hook types with their respective input field
|
|
|
407
409
|
- [🚀 Session Start Hooks](docs/API/SESSION_START.md)
|
|
408
410
|
- [🖋️ User Prompt Submit Hooks](docs/API/USER_PROMPT_SUBMIT.md)
|
|
409
411
|
- [🛠️ Pre-Tool Use Hooks](docs/API/PRE_TOOL_USE.md)
|
|
412
|
+
- [🔐 Permission Request Hooks](docs/API/PERMISSION_REQUEST.md)
|
|
410
413
|
- [🔧 Post-Tool Use Hooks](docs/API/POST_TOOL_USE.md)
|
|
411
414
|
- [📝 Pre-Compact Hooks](docs/API/PRE_COMPACT.md)
|
|
412
415
|
- [⏹️ Stop Hooks](docs/API/STOP.md)
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Upgrading to v1.1.0
|
|
2
|
+
|
|
3
|
+
Version 1.1.0 adds new features to align with Anthropic's updated Claude Code Hooks documentation. **All changes are backward compatible** - existing hooks continue to work without modification.
|
|
4
|
+
|
|
5
|
+
## What's New
|
|
6
|
+
|
|
7
|
+
### 1. New PermissionRequest Hook
|
|
8
|
+
|
|
9
|
+
Handle permission request events from Claude Code:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
class PermissionGuard < ClaudeHooks::PermissionRequest
|
|
13
|
+
def call
|
|
14
|
+
if dangerous_tool?
|
|
15
|
+
deny_permission!("Requires manual approval")
|
|
16
|
+
else
|
|
17
|
+
allow_permission!("Safe to proceed")
|
|
18
|
+
end
|
|
19
|
+
output
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def dangerous_tool?
|
|
25
|
+
%w[rm git-reset curl].include?(tool_name)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**When to use**: Create permission guards that automatically approve/deny permission requests based on custom logic.
|
|
31
|
+
|
|
32
|
+
### 2. New Fields Available
|
|
33
|
+
|
|
34
|
+
All hooks now receive these additional fields:
|
|
35
|
+
|
|
36
|
+
- **`permission_mode`** (all hooks): Access via `permission_mode` method
|
|
37
|
+
- Values: `'default'`, `'plan'`, `'acceptEdits'`, `'dontAsk'`, `'bypassPermissions'`
|
|
38
|
+
- Defaults to `'default'`
|
|
39
|
+
|
|
40
|
+
- **`tool_use_id`** (PreToolUse, PostToolUse, PermissionRequest): Access via `tool_use_id` method
|
|
41
|
+
- Unique identifier for each tool use (e.g., `"toolu_01ABC123..."`)
|
|
42
|
+
|
|
43
|
+
- **`notification_type`** (Notification): Access via `notification_type` method
|
|
44
|
+
- Values: `'permission_prompt'`, `'idle_prompt'`, `'auth_success'`, `'elicitation_dialog'`
|
|
45
|
+
|
|
46
|
+
**Example usage:**
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
class MyHook < ClaudeHooks::PreToolUse
|
|
50
|
+
def call
|
|
51
|
+
log "Permission mode: #{permission_mode}"
|
|
52
|
+
log "Tool use ID: #{tool_use_id}"
|
|
53
|
+
|
|
54
|
+
# Adjust behavior based on permission mode
|
|
55
|
+
if permission_mode == 'bypassPermissions'
|
|
56
|
+
# More restrictive checks
|
|
57
|
+
deny_permission!("Cannot bypass for sensitive operations")
|
|
58
|
+
else
|
|
59
|
+
approve_tool!("Approved")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
output
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 3. Update Tool Input (PreToolUse)
|
|
68
|
+
|
|
69
|
+
Modify tool input before execution:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
class InputModifier < ClaudeHooks::PreToolUse
|
|
73
|
+
def call
|
|
74
|
+
case tool_name
|
|
75
|
+
when 'Bash'
|
|
76
|
+
# Add safety flags to bash commands
|
|
77
|
+
if tool_input['command']&.include?('rm')
|
|
78
|
+
sanitized = tool_input.merge('command' => "#{tool_input['command']} --interactive")
|
|
79
|
+
update_tool_input!(sanitized)
|
|
80
|
+
else
|
|
81
|
+
approve_tool!("Safe command")
|
|
82
|
+
end
|
|
83
|
+
when 'Write'
|
|
84
|
+
# Validate file paths
|
|
85
|
+
if tool_input['file_path']&.start_with?('/etc')
|
|
86
|
+
deny_permission!("Cannot write to /etc")
|
|
87
|
+
else
|
|
88
|
+
approve_tool!("Safe write")
|
|
89
|
+
end
|
|
90
|
+
else
|
|
91
|
+
approve_tool!("No special handling")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
output
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Output helpers:**
|
|
100
|
+
- `output.input_updated?` - Check if input was modified
|
|
101
|
+
- `output.updated_input` - Get the updated input hash
|
|
102
|
+
|
|
103
|
+
## Do I Need to Change My Existing Hooks?
|
|
104
|
+
|
|
105
|
+
**No!** All changes are backward compatible:
|
|
106
|
+
|
|
107
|
+
- New fields have sensible defaults (return `nil` or `'default'`)
|
|
108
|
+
- Existing methods continue to work exactly as before
|
|
109
|
+
- No breaking changes to any APIs
|
|
110
|
+
- Your existing hooks will work with v1.1.0 without any modifications
|
|
111
|
+
|
|
112
|
+
## Should I Update My Hooks?
|
|
113
|
+
|
|
114
|
+
Consider updating if:
|
|
115
|
+
|
|
116
|
+
- ✅ You want to handle PermissionRequest events
|
|
117
|
+
- ✅ You need to check the current permission mode
|
|
118
|
+
- ✅ You want to modify tool inputs dynamically
|
|
119
|
+
- ✅ You need to differentiate notification types
|
|
120
|
+
- ✅ You want to track specific tool use instances via IDs
|
|
121
|
+
|
|
122
|
+
## New Hook Type Summary
|
|
123
|
+
|
|
124
|
+
| Hook Type | When It Runs | Key Methods |
|
|
125
|
+
|-----------|--------------|-------------|
|
|
126
|
+
| **PermissionRequest** | When Claude requests permission | `allow_permission!`, `deny_permission!`, `update_input_and_allow!` |
|
|
127
|
+
|
|
128
|
+
## New Fields Summary
|
|
129
|
+
|
|
130
|
+
| Field | Available In | Purpose |
|
|
131
|
+
|-------|--------------|---------|
|
|
132
|
+
| `permission_mode` | All hooks | Know the current permission mode |
|
|
133
|
+
| `tool_use_id` | PreToolUse, PermissionRequest, PostToolUse | Track specific tool use instances |
|
|
134
|
+
| `notification_type` | Notification | Filter notifications by type |
|
|
135
|
+
|
|
136
|
+
## New PreToolUse Features
|
|
137
|
+
|
|
138
|
+
| Feature | Method | Description |
|
|
139
|
+
|---------|--------|-------------|
|
|
140
|
+
| Update input | `update_tool_input!(hash)` | Modify tool input and auto-approve |
|
|
141
|
+
| Check if updated | `output.input_updated?` | Boolean check for modification |
|
|
142
|
+
| Get updated input | `output.updated_input` | Retrieve the modified input |
|
|
143
|
+
|
|
144
|
+
## Testing
|
|
145
|
+
|
|
146
|
+
All existing tests should pass without modification. Run:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
bundle exec rake test
|
|
150
|
+
# or
|
|
151
|
+
ruby test/run_all_tests.rb
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Migration Examples
|
|
155
|
+
|
|
156
|
+
### Example 1: Using permission_mode
|
|
157
|
+
|
|
158
|
+
**Before (v1.0.x):**
|
|
159
|
+
```ruby
|
|
160
|
+
class GitGuard < ClaudeHooks::PreToolUse
|
|
161
|
+
def call
|
|
162
|
+
if tool_name == 'Bash' && tool_input['command']&.include?('git push')
|
|
163
|
+
ask_for_permission!("Git push requires confirmation")
|
|
164
|
+
else
|
|
165
|
+
approve_tool!("Approved")
|
|
166
|
+
end
|
|
167
|
+
output
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**After (v1.1.0 - enhanced):**
|
|
173
|
+
```ruby
|
|
174
|
+
class GitGuard < ClaudeHooks::PreToolUse
|
|
175
|
+
def call
|
|
176
|
+
if tool_name == 'Bash' && tool_input['command']&.include?('git push')
|
|
177
|
+
# Skip asking in dontAsk mode, but still log
|
|
178
|
+
if permission_mode == 'dontAsk'
|
|
179
|
+
log "Auto-approving git push in dontAsk mode", level: :warn
|
|
180
|
+
approve_tool!("Auto-approved due to permission mode")
|
|
181
|
+
else
|
|
182
|
+
ask_for_permission!("Git push requires confirmation")
|
|
183
|
+
end
|
|
184
|
+
else
|
|
185
|
+
approve_tool!("Approved")
|
|
186
|
+
end
|
|
187
|
+
output
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Example 2: Using PermissionRequest
|
|
193
|
+
|
|
194
|
+
**Before (v1.0.x):**
|
|
195
|
+
```ruby
|
|
196
|
+
# Permission requests couldn't be handled automatically
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**After (v1.1.0 - new capability):**
|
|
200
|
+
```ruby
|
|
201
|
+
class AutoPermissionGuard < ClaudeHooks::PermissionRequest
|
|
202
|
+
SAFE_TOOLS = %w[ls cat grep find read].freeze
|
|
203
|
+
|
|
204
|
+
def call
|
|
205
|
+
if SAFE_TOOLS.include?(tool_name)
|
|
206
|
+
allow_permission!("Safe tool, auto-approved")
|
|
207
|
+
else
|
|
208
|
+
# Let user decide
|
|
209
|
+
deny_permission!("Requires manual approval")
|
|
210
|
+
end
|
|
211
|
+
output
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Example 3: Updating tool input
|
|
217
|
+
|
|
218
|
+
**Before (v1.0.x):**
|
|
219
|
+
```ruby
|
|
220
|
+
class BashSanitizer < ClaudeHooks::PreToolUse
|
|
221
|
+
def call
|
|
222
|
+
# Could only approve or deny, not modify
|
|
223
|
+
if tool_input['command']&.include?('rm')
|
|
224
|
+
block_tool!("Dangerous command blocked")
|
|
225
|
+
else
|
|
226
|
+
approve_tool!("Safe")
|
|
227
|
+
end
|
|
228
|
+
output
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**After (v1.1.0 - new capability):**
|
|
234
|
+
```ruby
|
|
235
|
+
class BashSanitizer < ClaudeHooks::PreToolUse
|
|
236
|
+
def call
|
|
237
|
+
if tool_input['command']&.include?('rm')
|
|
238
|
+
# Add safety flags instead of blocking
|
|
239
|
+
safe_command = "#{tool_input['command']} --interactive --verbose"
|
|
240
|
+
update_tool_input!({'command' => safe_command})
|
|
241
|
+
log "Added safety flags to rm command"
|
|
242
|
+
else
|
|
243
|
+
approve_tool!("Safe")
|
|
244
|
+
end
|
|
245
|
+
output
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Questions?
|
|
251
|
+
|
|
252
|
+
- See the [full CHANGELOG](../CHANGELOG.md) for all changes
|
|
253
|
+
- Check the [API documentation](API/) for detailed method references
|
|
254
|
+
- Review the [PermissionRequest API](API/PERMISSION_REQUEST.md) for the new hook type
|
|
255
|
+
- Look at [example implementations](../example_dotclaude/) for patterns
|
|
256
|
+
|
|
257
|
+
## Rollback
|
|
258
|
+
|
|
259
|
+
If you need to rollback to v1.0.2:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
gem uninstall claude_hooks
|
|
263
|
+
gem install claude_hooks -v 1.0.2
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Or in your Gemfile:
|
|
267
|
+
```ruby
|
|
268
|
+
gem 'claude_hooks', '~> 1.0.2'
|
|
269
|
+
```
|
data/docs/API/COMMON.md
CHANGED
|
@@ -12,6 +12,7 @@ Input helpers to access the data provided by Claude Code through `STDIN`.
|
|
|
12
12
|
| `transcript_path` | Get path to the transcript file |
|
|
13
13
|
| `cwd` | Get current working directory |
|
|
14
14
|
| `hook_event_name` | Get the hook event name |
|
|
15
|
+
| `permission_mode` | Get the permission mode: `'default'`, `'plan'`, `'acceptEdits'`, `'dontAsk'`, `'bypassPermissions'` (defaults to `'default'`) |
|
|
15
16
|
| `read_transcript` | Read the transcript file |
|
|
16
17
|
| `transcript` | Alias for `read_transcript` |
|
|
17
18
|
|
data/docs/API/NOTIFICATION.md
CHANGED
|
@@ -11,6 +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
15
|
|
|
15
16
|
## Hook State Helpers
|
|
16
17
|
Notifications are outside facing and do not have any specific state to modify.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# PermissionRequest API
|
|
2
|
+
|
|
3
|
+
Available when inheriting from `ClaudeHooks::PermissionRequest`:
|
|
4
|
+
|
|
5
|
+
## Input Helpers
|
|
6
|
+
Input helpers to access the data provided by Claude Code through `STDIN`.
|
|
7
|
+
|
|
8
|
+
[📚 Shared input helpers](COMMON.md#input-helpers)
|
|
9
|
+
|
|
10
|
+
| Method | Description |
|
|
11
|
+
|--------|-------------|
|
|
12
|
+
| `tool_name` | Get the name of the tool requiring permission |
|
|
13
|
+
| `tool_input` | Get the input data for the tool |
|
|
14
|
+
| `tool_use_id` | Get the unique identifier for this tool use |
|
|
15
|
+
|
|
16
|
+
## Hook State Helpers
|
|
17
|
+
Hook state methods are helpers to modify the hook's internal state (`output_data`) before yielding back to Claude Code.
|
|
18
|
+
|
|
19
|
+
[📚 Shared hook state methods](COMMON.md#hook-state-methods)
|
|
20
|
+
|
|
21
|
+
| Method | Description |
|
|
22
|
+
|--------|-------------|
|
|
23
|
+
| `allow_permission!(reason = '')` | Allow the permission request with optional reason |
|
|
24
|
+
| `deny_permission!(reason = '')` | Deny the permission request with reason |
|
|
25
|
+
| `update_input_and_allow!(updated_input, reason = '')` | Update tool input and allow (convenience method) |
|
|
26
|
+
|
|
27
|
+
## Output Helpers
|
|
28
|
+
Output helpers provide access to the hook's output data and helper methods for working with the output state.
|
|
29
|
+
|
|
30
|
+
[📚 Shared output helpers](COMMON.md#output-helpers)
|
|
31
|
+
|
|
32
|
+
| Method | Description |
|
|
33
|
+
|--------|-------------|
|
|
34
|
+
| `output.allowed?` | Check if permission has been allowed |
|
|
35
|
+
| `output.denied?` | Check if permission has been denied |
|
|
36
|
+
| `output.input_updated?` | Check if tool input has been updated |
|
|
37
|
+
| `output.permission_decision` | Get the permission decision: 'allow' or 'deny' |
|
|
38
|
+
| `output.permission_reason` | Get the reason for the permission decision |
|
|
39
|
+
| `output.updated_input` | Get the updated input (if provided) |
|
|
40
|
+
|
|
41
|
+
## Hook Exit Codes
|
|
42
|
+
|
|
43
|
+
PermissionRequest hooks use the JSON API with exit code 0 for all permission decisions.
|
|
44
|
+
|
|
45
|
+
| Exit Code | Behavior |
|
|
46
|
+
|-----------|----------|
|
|
47
|
+
| `exit 0` | Permission decision processed<br/>`STDOUT` contains JSON with decision |
|
|
48
|
+
| `exit 1` | Non-blocking error<br/>`STDERR` shown to user |
|
|
49
|
+
| `exit 2` | **Not recommended for PermissionRequest**<br/>Use JSON API with exit 0 instead |
|
|
50
|
+
|
|
51
|
+
## Example: Basic Permission Guard
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
#!/usr/bin/env ruby
|
|
55
|
+
require 'claude_hooks'
|
|
56
|
+
|
|
57
|
+
class PermissionGuard < ClaudeHooks::PermissionRequest
|
|
58
|
+
SENSITIVE_TOOLS = %w[rm git-push curl wget].freeze
|
|
59
|
+
SAFE_TOOLS = %w[ls cat grep find].freeze
|
|
60
|
+
|
|
61
|
+
def call
|
|
62
|
+
log "Permission requested for: #{tool_name}"
|
|
63
|
+
log "Tool use ID: #{tool_use_id}"
|
|
64
|
+
log "Permission mode: #{permission_mode}"
|
|
65
|
+
|
|
66
|
+
# Auto-allow safe tools
|
|
67
|
+
if SAFE_TOOLS.include?(tool_name)
|
|
68
|
+
allow_permission!("Safe tool, automatically allowed")
|
|
69
|
+
return output
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Block dangerous tools
|
|
73
|
+
if SENSITIVE_TOOLS.include?(tool_name)
|
|
74
|
+
deny_permission!("Dangerous tool #{tool_name} requires manual approval")
|
|
75
|
+
return output
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Default: allow with logging
|
|
79
|
+
allow_permission!("Tool allowed by default")
|
|
80
|
+
output
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if __FILE__ == $PROGRAM_NAME
|
|
85
|
+
input_data = JSON.parse(STDIN.read)
|
|
86
|
+
hook = PermissionGuard.new(input_data)
|
|
87
|
+
hook.call
|
|
88
|
+
hook.output_and_exit
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Example: Permission Mode-Aware Guard
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
#!/usr/bin/env ruby
|
|
96
|
+
require 'claude_hooks'
|
|
97
|
+
|
|
98
|
+
class SmartPermissionGuard < ClaudeHooks::PermissionRequest
|
|
99
|
+
DANGEROUS_TOOLS = %w[rm git-reset curl wget].freeze
|
|
100
|
+
|
|
101
|
+
def call
|
|
102
|
+
log "Permission mode: #{permission_mode}"
|
|
103
|
+
|
|
104
|
+
# Block dangerous tools in bypass mode
|
|
105
|
+
if permission_mode == 'bypassPermissions' && dangerous_tool?
|
|
106
|
+
log "Blocking dangerous tool in bypass mode", level: :warn
|
|
107
|
+
deny_permission!("Cannot bypass permissions for dangerous tool: #{tool_name}")
|
|
108
|
+
return output
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Allow in 'dontAsk' mode if tool is not dangerous
|
|
112
|
+
if permission_mode == 'dontAsk' && !dangerous_tool?
|
|
113
|
+
allow_permission!("Auto-allowed in dontAsk mode")
|
|
114
|
+
return output
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Default behavior
|
|
118
|
+
allow_permission!("Permission granted")
|
|
119
|
+
output
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def dangerous_tool?
|
|
125
|
+
DANGEROUS_TOOLS.include?(tool_name)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Example: Input Modification
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
#!/usr/bin/env ruby
|
|
134
|
+
require 'claude_hooks'
|
|
135
|
+
|
|
136
|
+
class InputSanitizer < ClaudeHooks::PermissionRequest
|
|
137
|
+
def call
|
|
138
|
+
case tool_name
|
|
139
|
+
when 'Bash'
|
|
140
|
+
# Add safety flags to bash commands
|
|
141
|
+
if tool_input['command']&.include?('rm')
|
|
142
|
+
sanitized_input = tool_input.merge(
|
|
143
|
+
'command' => tool_input['command'] + ' --interactive'
|
|
144
|
+
)
|
|
145
|
+
update_input_and_allow!(sanitized_input, "Added --interactive flag for safety")
|
|
146
|
+
else
|
|
147
|
+
allow_permission!("Bash command is safe")
|
|
148
|
+
end
|
|
149
|
+
when 'Write'
|
|
150
|
+
# Validate file paths before writing
|
|
151
|
+
if tool_input['file_path']&.start_with?('/tmp')
|
|
152
|
+
deny_permission!("Cannot write to /tmp directory")
|
|
153
|
+
else
|
|
154
|
+
allow_permission!("File write is safe")
|
|
155
|
+
end
|
|
156
|
+
else
|
|
157
|
+
allow_permission!("No special handling needed")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
output
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Multiple Hooks Merging
|
|
166
|
+
|
|
167
|
+
When multiple PermissionRequest hooks are executed, their outputs merge with these rules:
|
|
168
|
+
|
|
169
|
+
- **Decision**: `deny` > `allow` (most restrictive wins)
|
|
170
|
+
- **Reasons**: All reasons are concatenated with `'; '`
|
|
171
|
+
- **Updated Input**: Last updated input wins (most recent transformation)
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
# Hook 1
|
|
175
|
+
hook1.deny_permission!("Reason 1")
|
|
176
|
+
|
|
177
|
+
# Hook 2
|
|
178
|
+
hook2.allow_permission!("Reason 2")
|
|
179
|
+
|
|
180
|
+
# Merged result
|
|
181
|
+
merged = ClaudeHooks::Output::PermissionRequest.merge(
|
|
182
|
+
hook1.output,
|
|
183
|
+
hook2.output
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
merged.denied? # => true (deny wins)
|
|
187
|
+
merged.permission_reason # => "Reason 1; Reason 2"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Notes
|
|
191
|
+
|
|
192
|
+
- PermissionRequest runs when Claude Code shows a permission dialog
|
|
193
|
+
- It can automatically allow or deny on behalf of the user
|
|
194
|
+
- Uses the same JSON API pattern as PreToolUse hooks
|
|
195
|
+
- Supports modifying tool inputs before execution
|
|
196
|
+
- Works with all permission modes: `default`, `plan`, `acceptEdits`, `dontAsk`, `bypassPermissions`
|
data/docs/API/POST_TOOL_USE.md
CHANGED
|
@@ -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 that was used |
|
|
13
13
|
| `tool_input` | Get the input that was passed to the tool |
|
|
14
14
|
| `tool_response` | Get the tool's response/output |
|
|
15
|
+
| `tool_use_id` | Get the unique identifier for this tool use (e.g., `"toolu_01ABC123..."`) |
|
|
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.
|
data/docs/API/PRE_TOOL_USE.md
CHANGED
|
@@ -11,6 +11,7 @@ Input helpers to access the data provided by Claude Code through `STDIN`.
|
|
|
11
11
|
|--------|-------------|
|
|
12
12
|
| `tool_name` | Get the name of the tool being used |
|
|
13
13
|
| `tool_input` | Get the input data for the tool |
|
|
14
|
+
| `tool_use_id` | Get the unique identifier for this tool use (e.g., `"toolu_01ABC123..."`) |
|
|
14
15
|
|
|
15
16
|
## Hook State Helpers
|
|
16
17
|
Hook state methods are helpers to modify the hook's internal state (`output_data`) before yielding back to Claude Code.
|
|
@@ -22,6 +23,7 @@ Hook state methods are helpers to modify the hook's internal state (`output_data
|
|
|
22
23
|
| `approve_tool!(reason)` | Explicitly approve tool usage |
|
|
23
24
|
| `block_tool!(reason)` | Block tool usage with feedback |
|
|
24
25
|
| `ask_for_permission!(reason)` | Request user permission |
|
|
26
|
+
| `update_tool_input!(updated_input)` | Update the tool input and automatically approve the tool (sets `permissionDecision` to `'allow'`) |
|
|
25
27
|
|
|
26
28
|
## Output Helpers
|
|
27
29
|
Output helpers provide access to the hook's output data and helper methods for working with the output state.
|
|
@@ -34,8 +36,10 @@ Output helpers provide access to the hook's output data and helper methods for w
|
|
|
34
36
|
| `output.denied?` | Check if the tool has been denied (permission_decision == 'deny') |
|
|
35
37
|
| `output.blocked?` | Alias for `denied?` |
|
|
36
38
|
| `output.should_ask_permission?` | Check if user permission is required (permission_decision == 'ask') |
|
|
39
|
+
| `output.input_updated?` | Check if tool input has been updated |
|
|
37
40
|
| `output.permission_decision` | Get the permission decision: 'allow', 'deny', or 'ask' |
|
|
38
41
|
| `output.permission_reason` | Get the reason for the permission decision |
|
|
42
|
+
| `output.updated_input` | Get the updated input (if provided) |
|
|
39
43
|
|
|
40
44
|
## Hook Exit Codes
|
|
41
45
|
|