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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d82c5deaa5426bc2ca898300f7772fa58c56a2998fd00af4eb1a93de25d3336e
4
- data.tar.gz: ffde711d996ad0f43f7f75a3002f5a1e74e84f28ed2197ddf9c53c8b982a5152
3
+ metadata.gz: d7cd63a0fbf165b912baa3fab487e056f05bc10d29b77123e2029ef1dce385a4
4
+ data.tar.gz: '054837c4d15be8b9d248aa81db5966d80c2c5e0f9a322c622bdf781bf8d0af20'
5
5
  SHA512:
6
- metadata.gz: 8256eeed53e717873271ea94b9d85610e741ade7d6009b41a9df6eccc8ea58ec4af3b4ab8883d8dfba3d0ef849afdcae6939786d7cb7ad4d000dd7dc749a73ab
7
- data.tar.gz: 306144f71bd099d70df514ee21eb9e63a11389562322927465b1aecff447c78eaafc602aa973db8a925bf0885c4e212fbcbfacb410c6d34c30a7b56eb9e51145
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,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
- | **PostToolUse** | `tool_name`, `tool_input`, `tool_response` |
394
- | **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` |
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
 
@@ -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`
@@ -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.
@@ -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