claude_hooks 0.2.1 → 1.0.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/CHANGELOG.md +92 -0
- data/README.md +215 -357
- data/claude_hooks.gemspec +2 -2
- data/docs/API/COMMON.md +83 -0
- data/docs/API/NOTIFICATION.md +32 -0
- data/docs/API/POST_TOOL_USE.md +45 -0
- data/docs/API/PRE_COMPACT.md +39 -0
- data/docs/API/PRE_TOOL_USE.md +110 -0
- data/docs/API/SESSION_END.md +100 -0
- data/docs/API/SESSION_START.md +40 -0
- data/docs/API/STOP.md +47 -0
- data/docs/API/SUBAGENT_STOP.md +47 -0
- data/docs/API/USER_PROMPT_SUBMIT.md +47 -0
- data/docs/OUTPUT_MIGRATION_GUIDE.md +228 -0
- data/example_dotclaude/hooks/entrypoints/session_end.rb +35 -0
- data/example_dotclaude/hooks/entrypoints/user_prompt_submit.rb +17 -12
- data/example_dotclaude/hooks/handlers/session_end/cleanup_handler.rb +55 -0
- data/example_dotclaude/hooks/handlers/session_end/log_session_stats.rb +64 -0
- data/example_dotclaude/hooks/handlers/user_prompt_submit/append_rules.rb +1 -1
- data/example_dotclaude/hooks/handlers/user_prompt_submit/log_user_prompt.rb +2 -2
- data/example_dotclaude/settings.json +11 -0
- data/lib/claude_hooks/base.rb +16 -24
- data/lib/claude_hooks/cli.rb +75 -1
- data/lib/claude_hooks/logger.rb +0 -1
- data/lib/claude_hooks/output/base.rb +152 -0
- data/lib/claude_hooks/output/notification.rb +22 -0
- data/lib/claude_hooks/output/post_tool_use.rb +68 -0
- data/lib/claude_hooks/output/pre_compact.rb +20 -0
- data/lib/claude_hooks/output/pre_tool_use.rb +97 -0
- data/lib/claude_hooks/output/session_end.rb +24 -0
- data/lib/claude_hooks/output/session_start.rb +49 -0
- data/lib/claude_hooks/output/stop.rb +76 -0
- data/lib/claude_hooks/output/subagent_stop.rb +14 -0
- data/lib/claude_hooks/output/user_prompt_submit.rb +71 -0
- data/lib/claude_hooks/post_tool_use.rb +6 -12
- data/lib/claude_hooks/pre_tool_use.rb +0 -37
- data/lib/claude_hooks/session_end.rb +43 -0
- data/lib/claude_hooks/session_start.rb +0 -23
- data/lib/claude_hooks/stop.rb +0 -25
- data/lib/claude_hooks/user_prompt_submit.rb +0 -26
- data/lib/claude_hooks/version.rb +1 -1
- data/lib/claude_hooks.rb +15 -0
- metadata +33 -8
- /data/{WHY.md → docs/WHY.md} +0 -0
data/README.md
CHANGED
@@ -2,10 +2,28 @@
|
|
2
2
|
|
3
3
|
A Ruby DSL (Domain Specific Language) for creating Claude Code hooks. This will hopefully make creating and configuring new hooks way easier.
|
4
4
|
|
5
|
-
[**Why use this instead of writing bash, or simple ruby scripts?**](WHY.md)
|
5
|
+
[**Why use this instead of writing bash, or simple ruby scripts?**](docs/WHY.md)
|
6
6
|
|
7
7
|
> 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
8
|
|
9
|
+
## 📖 Table of Contents
|
10
|
+
|
11
|
+
- [Ruby DSL for Claude Code hooks](#ruby-dsl-for-claude-code-hooks)
|
12
|
+
- [📖 Table of Contents](#-table-of-contents)
|
13
|
+
- [🚀 Quick Start](#-quick-start)
|
14
|
+
- [📦 Installation](#-installation)
|
15
|
+
- [🏗️ Architecture](#️-architecture)
|
16
|
+
- [🪝 Hook Types](#-hook-types)
|
17
|
+
- [🚀 Claude Hook Flow](#-claude-hook-flow)
|
18
|
+
- [📚 API Reference](#-api-reference)
|
19
|
+
- [📝 Example: Tool usage monitor](#-example-tool-usage-monitor)
|
20
|
+
- [🔄 Hook Output](#-hook-output)
|
21
|
+
- [🚨 Advices](#-advices)
|
22
|
+
- [⚠️ Troubleshooting](#️-troubleshooting)
|
23
|
+
- [🧪 CLI Debugging](#-cli-debugging)
|
24
|
+
- [🐛 Debugging](#-debugging)
|
25
|
+
- [🧪 Development \& Contributing](#-development--contributing)
|
26
|
+
|
9
27
|
## 🚀 Quick Start
|
10
28
|
|
11
29
|
> [!TIP]
|
@@ -29,7 +47,7 @@ class AddContextAfterPrompt < ClaudeHooks::UserPromptSubmit
|
|
29
47
|
def call
|
30
48
|
log "User asked: #{prompt}"
|
31
49
|
add_context!("Remember to be extra helpful!")
|
32
|
-
|
50
|
+
output
|
33
51
|
end
|
34
52
|
end
|
35
53
|
|
@@ -39,10 +57,11 @@ if __FILE__ == $0
|
|
39
57
|
input_data = JSON.parse(STDIN.read)
|
40
58
|
|
41
59
|
hook = AddContextAfterPrompt.new(input_data)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
exit 0
|
60
|
+
hook.call
|
61
|
+
|
62
|
+
# Handles output and exit code depending on the hook state.
|
63
|
+
# In this case, uses exit code 0 (success) and prints output to STDOUT
|
64
|
+
hook.output_and_exit
|
46
65
|
end
|
47
66
|
```
|
48
67
|
|
@@ -76,16 +95,18 @@ That's it! Your hook will now add context to every user prompt. 🎉
|
|
76
95
|
|
77
96
|
## 📦 Installation
|
78
97
|
|
79
|
-
Install it globally (simpler):
|
98
|
+
### Install it globally (simpler):
|
80
99
|
|
81
100
|
```bash
|
82
101
|
$ gem install claude_hooks
|
83
102
|
```
|
84
103
|
|
85
|
-
|
104
|
+
### Using a Gemfile
|
86
105
|
|
87
|
-
|
106
|
+
> [!WARNING]
|
107
|
+
> Unless you use `bundle exec` in the command in your `.claude/settings.json`, Claude Code will use the system-installed gem, not the bundled version.
|
88
108
|
|
109
|
+
Add it to your Gemfile (you can add a Gemfile in your `.claude` directory if needed):
|
89
110
|
|
90
111
|
```ruby
|
91
112
|
# .claude/Gemfile
|
@@ -100,9 +121,6 @@ And then run:
|
|
100
121
|
$ bundle install
|
101
122
|
```
|
102
123
|
|
103
|
-
> [!WARNING]
|
104
|
-
> If you use a Gemfile, you need to use `bundle exec` to run your hooks in your `.claude/settings.json`.
|
105
|
-
|
106
124
|
### 🔧 Configuration
|
107
125
|
|
108
126
|
Claude Hooks supports both home-level (`$HOME/.claude`) and project-level (`$CLAUDE_PROJECT_DIR/.claude`) directories. Claude Hooks specific config files (`config/config.json`) found in either directory will be merged together.
|
@@ -121,7 +139,7 @@ You can configure Claude Hooks through environment variables with the `RUBY_CLAU
|
|
121
139
|
|
122
140
|
```bash
|
123
141
|
# Existing configuration options
|
124
|
-
export RUBY_CLAUDE_HOOKS_LOG_DIR="logs" # Default: logs (relative to HOME/.claude)
|
142
|
+
export RUBY_CLAUDE_HOOKS_LOG_DIR="logs" # Default: logs (relative to $HOME/.claude)
|
125
143
|
export RUBY_CLAUDE_HOOKS_CONFIG_MERGE_STRATEGY="project" # Config merge strategy: "project" or "home", default: "project"
|
126
144
|
export RUBY_CLAUDE_HOOKS_BASE_DIR="~/.claude" # DEPRECATED: fallback base directory
|
127
145
|
|
@@ -193,97 +211,20 @@ class MyHandler < ClaudeHooks::UserPromptSubmit
|
|
193
211
|
user_name = config.get_config_value('USER_NAME', 'userName')
|
194
212
|
log "Username: #{user_name}"
|
195
213
|
|
196
|
-
|
214
|
+
output
|
197
215
|
end
|
198
216
|
end
|
199
217
|
```
|
200
218
|
|
201
|
-
## 📖 Table of Contents
|
202
|
-
|
203
|
-
- [Ruby DSL for Claude Code hooks](#ruby-dsl-for-claude-code-hooks)
|
204
|
-
- [🚀 Quick Start](#-quick-start)
|
205
|
-
- [📦 Installation](#-installation)
|
206
|
-
- [🔧 Configuration](#-configuration)
|
207
|
-
- [Environment Variables](#environment-variables)
|
208
|
-
- [Configuration Files](#configuration-files)
|
209
|
-
- [Configuration Merging](#configuration-merging)
|
210
|
-
- [Accessing Configuration Variables](#accessing-configuration-variables)
|
211
|
-
- [📖 Table of Contents](#-table-of-contents)
|
212
|
-
- [🏗️ Architecture](#️-architecture)
|
213
|
-
- [Core Components](#core-components)
|
214
|
-
- [Recommended structure for your .claude/hooks/ directory](#recommended-structure-for-your-claudehooks-directory)
|
215
|
-
- [🪝 Hook Types](#-hook-types)
|
216
|
-
- [🚀 Claude Hook Flow](#-claude-hook-flow)
|
217
|
-
- [A very simplified view of how a hook works in Claude Code](#a-very-simplified-view-of-how-a-hook-works-in-claude-code)
|
218
|
-
- [🔄 Proposal: a more robust Claude Hook execution flow](#-proposal-a-more-robust-claude-hook-execution-flow)
|
219
|
-
- [Basic Hook Handler Structure](#basic-hook-handler-structure)
|
220
|
-
- [Input Fields](#input-fields)
|
221
|
-
- [📚 API Reference](#-api-reference)
|
222
|
-
- [Common API Methods](#common-api-methods)
|
223
|
-
- [Input Methods](#input-methods)
|
224
|
-
- [Output Methods](#output-methods)
|
225
|
-
- [Class Output Methods](#class-output-methods)
|
226
|
-
- [Configuration and Utility Methods](#configuration-and-utility-methods)
|
227
|
-
- [Utility Methods](#utility-methods)
|
228
|
-
- [Configuration Methods](#configuration-methods)
|
229
|
-
- [UserPromptSubmit API](#userpromptsubmit-api)
|
230
|
-
- [Input Methods](#input-methods-1)
|
231
|
-
- [Output Methods](#output-methods-1)
|
232
|
-
- [PreToolUse API](#pretooluse-api)
|
233
|
-
- [Input Methods](#input-methods-2)
|
234
|
-
- [Output Methods](#output-methods-2)
|
235
|
-
- [PostToolUse API](#posttooluse-api)
|
236
|
-
- [Input Methods](#input-methods-3)
|
237
|
-
- [Output Methods](#output-methods-3)
|
238
|
-
- [Notification API](#notification-api)
|
239
|
-
- [Input Methods](#input-methods-4)
|
240
|
-
- [Output Methods](#output-methods-4)
|
241
|
-
- [Stop API](#stop-api)
|
242
|
-
- [Input Methods](#input-methods-5)
|
243
|
-
- [Output Methods](#output-methods-5)
|
244
|
-
- [SubagentStop API](#subagentstop-api)
|
245
|
-
- [Input Methods](#input-methods-6)
|
246
|
-
- [Output Methods](#output-methods-6)
|
247
|
-
- [PreCompact API](#precompact-api)
|
248
|
-
- [Input Methods](#input-methods-7)
|
249
|
-
- [Output Methods](#output-methods-7)
|
250
|
-
- [Utility Methods](#utility-methods-1)
|
251
|
-
- [SessionStart API](#sessionstart-api)
|
252
|
-
- [Input Methods](#input-methods-8)
|
253
|
-
- [Output Methods](#output-methods-8)
|
254
|
-
- [📝 Logging](#-logging)
|
255
|
-
- [Log File Location](#log-file-location)
|
256
|
-
- [Log Output Format](#log-output-format)
|
257
|
-
- [📝 Example: Tool usage monitor](#-example-tool-usage-monitor)
|
258
|
-
- [🔄 Hook Output](#-hook-output)
|
259
|
-
- [🔄 Hook Output Merging](#-hook-output-merging)
|
260
|
-
- [🚪 Hook Exit Codes](#-hook-exit-codes)
|
261
|
-
- [Pattern 1: Simple Exit Codes](#pattern-1-simple-exit-codes)
|
262
|
-
- [Example: Success](#example-success)
|
263
|
-
- [Example: Error](#example-error)
|
264
|
-
- [🚨 Advices](#-advices)
|
265
|
-
- [⚠️ Troubleshooting](#️-troubleshooting)
|
266
|
-
- [Make your entrypoint scripts executable](#make-your-entrypoint-scripts-executable)
|
267
|
-
- [🧪 CLI Debugging](#-cli-debugging)
|
268
|
-
- [Basic Usage](#basic-usage)
|
269
|
-
- [Customization with Blocks](#customization-with-blocks)
|
270
|
-
- [Testing Methods](#testing-methods)
|
271
|
-
- [1. Test with STDIN (default)](#1-test-with-stdin-default)
|
272
|
-
- [2. Test with default sample data instead of STDIN](#2-test-with-default-sample-data-instead-of-stdin)
|
273
|
-
- [3. Test with Sample Data + Customization](#3-test-with-sample-data--customization)
|
274
|
-
- [Example Hook with CLI Testing](#example-hook-with-cli-testing)
|
275
|
-
- [🐛 Debugging](#-debugging)
|
276
|
-
- [Test an individual entrypoint](#test-an-individual-entrypoint)
|
277
|
-
|
278
|
-
|
279
219
|
## 🏗️ Architecture
|
280
220
|
|
281
221
|
### Core Components
|
282
222
|
|
283
223
|
1. **`ClaudeHooks::Base`** - Base class with common functionality (logging, config, validation)
|
284
|
-
2. **Hook Handler Classes** - Self-contained classes (`ClaudeHooks::UserPromptSubmit`, `ClaudeHooks::PreToolUse`,
|
285
|
-
3. **
|
224
|
+
2. **Hook Handler Classes** - Self-contained classes (`ClaudeHooks::UserPromptSubmit`, `ClaudeHooks::PreToolUse`, etc.)
|
225
|
+
3. **Output Classes** - `ClaudeHooks::Output::UserPromptSubmit`, etc... are output objects that handle intelligent merging of multiple outputs, as well as using the right exit codes and outputting to the proper stream (`STDIN` or `STDERR`) depending on the the hook state.
|
286
226
|
4. **Configuration** - Shared configuration management via `ClaudeHooks::Configuration`
|
227
|
+
5. **Logger** - Dedicated logging class with multiline block support
|
287
228
|
|
288
229
|
### Recommended structure for your .claude/hooks/ directory
|
289
230
|
|
@@ -303,6 +244,7 @@ end
|
|
303
244
|
│ ├── append_rules.rb
|
304
245
|
│ └── log_user_prompt.rb
|
305
246
|
├── pre_tool_use/
|
247
|
+
│ ├── github_guard.rb
|
306
248
|
│ └── tool_monitor.rb
|
307
249
|
└── ...
|
308
250
|
```
|
@@ -313,24 +255,35 @@ The framework supports the following hook types:
|
|
313
255
|
|
314
256
|
| Hook Type | Class | Description |
|
315
257
|
|-----------|-------|-------------|
|
316
|
-
| **SessionStart** | `ClaudeHooks::SessionStart` | Hooks that run when Claude Code starts a new session or resumes |
|
317
|
-
| **UserPromptSubmit** | `ClaudeHooks::UserPromptSubmit` | Hooks that run before the user's prompt is processed |
|
318
|
-
| **Notification** | `ClaudeHooks::Notification` | Hooks that run when Claude Code sends notifications |
|
319
|
-
| **PreToolUse** | `ClaudeHooks::PreToolUse` | Hooks that run before a tool is used |
|
320
|
-
| **PostToolUse** | `ClaudeHooks::PostToolUse` | Hooks that run after a tool is used |
|
321
|
-
| **Stop** | `ClaudeHooks::Stop` | Hooks that run when Claude Code finishes responding |
|
322
|
-
| **SubagentStop** | `ClaudeHooks::SubagentStop` | Hooks that run when subagent tasks complete |
|
323
|
-
| **
|
258
|
+
| **[SessionStart](docs/API/SESSION_START.md)** | `ClaudeHooks::SessionStart` | Hooks that run when Claude Code starts a new session or resumes |
|
259
|
+
| **[UserPromptSubmit](docs/API/USER_PROMPT_SUBMIT.md)** | `ClaudeHooks::UserPromptSubmit` | Hooks that run before the user's prompt is processed |
|
260
|
+
| **[Notification](docs/API/NOTIFICATION.md)** | `ClaudeHooks::Notification` | Hooks that run when Claude Code sends notifications |
|
261
|
+
| **[PreToolUse](docs/API/PRE_TOOL_USE.md)** | `ClaudeHooks::PreToolUse` | Hooks that run before a tool is used |
|
262
|
+
| **[PostToolUse](docs/API/POST_TOOL_USE.md)** | `ClaudeHooks::PostToolUse` | Hooks that run after a tool is used |
|
263
|
+
| **[Stop](docs/API/STOP.md)** | `ClaudeHooks::Stop` | Hooks that run when Claude Code finishes responding |
|
264
|
+
| **[SubagentStop](docs/API/SUBAGENT_STOP.md)** | `ClaudeHooks::SubagentStop` | Hooks that run when subagent tasks complete |
|
265
|
+
| **[SessionEnd](docs/API/SESSION_END.md)** | `ClaudeHooks::SessionEnd` | Hooks that run when Claude Code sessions end |
|
266
|
+
| **[PreCompact](docs/API/PRE_COMPACT.md)** | `ClaudeHooks::PreCompact` | Hooks that run before transcript compaction |
|
324
267
|
|
325
268
|
## 🚀 Claude Hook Flow
|
326
269
|
|
327
270
|
### A very simplified view of how a hook works in Claude Code
|
328
271
|
|
272
|
+
Claude Code hooks in essence work in a very simple way:
|
273
|
+
- Claude Code passes data to the hook script through `STDIN`
|
274
|
+
- The hook uses the data to do its thing
|
275
|
+
- The hook outputs data to `STDOUT` or `STDERR` and then `exit`s with the proper code:
|
276
|
+
- `exit 0` for success
|
277
|
+
- `exit 1` for a non-blocking error
|
278
|
+
- `exit 2` for a blocking error (prevent Claude from continuing)
|
279
|
+
|
329
280
|
```mermaid
|
330
281
|
graph LR
|
331
|
-
A[Hook triggers] --> B[JSON from STDIN] --> C[Hook does its thing] --> D[JSON to STDOUT or STDERR] --> E[Yields back to Claude Code] --> A
|
282
|
+
A[Hook triggers] --> B[JSON from STDIN] --> C[Hook does its thing] --> D[JSON to STDOUT or STDERR<br />Exit Code] --> E[Yields back to Claude Code] --> A
|
332
283
|
```
|
333
284
|
|
285
|
+
The main issue is that there are many different types of hooks and they each have different expectations regarding the data outputted to `STDIN` or `STDERR` and Claude Code will react differently for each specific exit code used depending on the hook type.
|
286
|
+
|
334
287
|
### 🔄 Proposal: a more robust Claude Hook execution flow
|
335
288
|
|
336
289
|
1. An entrypoint for a hook is set in `~/.claude/settings.json`
|
@@ -338,7 +291,7 @@ graph LR
|
|
338
291
|
3. The entrypoint script reads STDIN and coordinates multiple **hook handlers**
|
339
292
|
4. Each **hook handler** executes and returns its output data
|
340
293
|
5. The entrypoint script combines/processes outputs from multiple **hook handlers**
|
341
|
-
6. And then returns final
|
294
|
+
6. And then returns final response to Claude Code with the correct exit code
|
342
295
|
|
343
296
|
```mermaid
|
344
297
|
graph TD
|
@@ -348,13 +301,13 @@ graph TD
|
|
348
301
|
C --> D[📋 Entrypoint<br />Parses JSON from STDIN]
|
349
302
|
D --> E[📋 Entrypoint<br />Calls hook handlers]
|
350
303
|
|
351
|
-
E --> F[📝 Handler<br />AppendContextRules.call<br/><em>Returns
|
352
|
-
E --> G[📝 Handler<br />PromptGuard.call<br/><em>Returns
|
304
|
+
E --> F[📝 Handler<br />AppendContextRules.call<br/><em>Returns output</em>]
|
305
|
+
E --> G[📝 Handler<br />PromptGuard.call<br/><em>Returns output</em>]
|
353
306
|
|
354
|
-
F --> J[📋 Entrypoint<br />Calls _ClaudeHooks::UserPromptSubmit.
|
307
|
+
F --> J[📋 Entrypoint<br />Calls _ClaudeHooks::Output::UserPromptSubmit.merge_ to 🔀 merge outputs]
|
355
308
|
G --> J
|
356
309
|
|
357
|
-
J --> K[📋 Entrypoint<br
|
310
|
+
J --> K[📋 Entrypoint<br />- Writes output to STDOUT or STDERR<br />- Uses correct exit code]
|
358
311
|
K --> L[🤖 Yields back to Claude Code]
|
359
312
|
L --> B
|
360
313
|
```
|
@@ -380,20 +333,48 @@ class AddContextAfterPrompt < ClaudeHooks::UserPromptSubmit
|
|
380
333
|
|
381
334
|
log "Full conversation transcript: #{read_transcript}"
|
382
335
|
|
336
|
+
# Use a Hook state method to modify what's sent back to Claude Code
|
383
337
|
add_additional_context!("Some custom context")
|
384
338
|
|
385
|
-
#
|
339
|
+
# Control execution, for instance: block the prompt
|
386
340
|
if current_prompt.include?("bad word")
|
387
341
|
block_prompt!("Hmm no no no!")
|
388
342
|
log "Prompt blocked: #{current_prompt} because of bad word"
|
389
343
|
end
|
390
344
|
|
391
|
-
# Return output
|
392
|
-
|
345
|
+
# Return output if you need it
|
346
|
+
output
|
393
347
|
end
|
394
348
|
end
|
349
|
+
|
350
|
+
# Use your handler (usually from an entrypoint file, but this is an example)
|
351
|
+
if __FILE__ == $0
|
352
|
+
# Read Claude Code's input data from STDIN
|
353
|
+
input_data = JSON.parse(STDIN.read)
|
354
|
+
|
355
|
+
hook = AddContextAfterPrompt.new(input_data)
|
356
|
+
# Call the hook
|
357
|
+
hook.call
|
358
|
+
|
359
|
+
# Uses exit code 0 (success) and outputs to STDIN if the prompt wasn't blocked
|
360
|
+
# Uses exit code 2 (blocking error) and outputs to STDERR if the prompt was blocked
|
361
|
+
hook.output_and_exit
|
362
|
+
end
|
395
363
|
```
|
396
364
|
|
365
|
+
## 📚 API Reference
|
366
|
+
|
367
|
+
The goal of those APIs is to simplify reading from `STDIN` and writing to `STDOUT` or `STDERR` as well as exiting with the right exit codes: the way Claude Code expects you to.
|
368
|
+
|
369
|
+
Each hook provides the following capabilities:
|
370
|
+
|
371
|
+
| Category | Description |
|
372
|
+
|----------|-------------|
|
373
|
+
| Configuration & Utility | Access config, logging, and file path helpers |
|
374
|
+
| Input Helpers | Access data parsed from STDIN (`session_id`, `transcript_path`, etc.) |
|
375
|
+
| Hook State Helpers | Modify the hook's internal state (adding additional context, blocking a tool call, etc...) before yielding back to Claude Code |
|
376
|
+
| Output Helpers | Access output data, merge results, and yield back to Claude with the proper exit codes |
|
377
|
+
|
397
378
|
### Input Fields
|
398
379
|
|
399
380
|
The framework supports all existing hook types with their respective input fields:
|
@@ -409,209 +390,26 @@ The framework supports all existing hook types with their respective input field
|
|
409
390
|
| **SubagentStop** | `stop_hook_active` |
|
410
391
|
| **PreCompact** | `trigger`, `custom_instructions` |
|
411
392
|
| **SessionStart** | `source` |
|
393
|
+
| **SessionEnd** | `reason` |
|
412
394
|
|
413
|
-
|
414
|
-
|
415
|
-
The whole purpose of those APIs is to simplify reading from STDIN and writing to STDOUT the way Claude Code expects you to.
|
416
|
-
|
417
|
-
### Common API Methods
|
418
|
-
|
419
|
-
Those methods are available in **all hook types** and are inherited from `ClaudeHooks::Base`:
|
395
|
+
### Hooks API
|
420
396
|
|
421
|
-
|
422
|
-
Input methods are helpers to access data parsed from STDIN.
|
423
|
-
|
424
|
-
| Method | Description |
|
425
|
-
|--------|-------------|
|
426
|
-
| `input_data` | Input data reader |
|
427
|
-
| `session_id` | Get the current session ID |
|
428
|
-
| `transcript_path` | Get path to the transcript file |
|
429
|
-
| `cwd` | Get current working directory |
|
430
|
-
| `hook_event_name` | Get the hook event name |
|
431
|
-
| `read_transcript` | Read the transcript file |
|
432
|
-
| `transcript` | Alias for `read_transcript` |
|
433
|
-
|
434
|
-
#### Output Methods
|
435
|
-
Output methods are helpers to modify `output_data`.
|
397
|
+
**All hook types** inherit from `ClaudeHooks::Base` and share a common API, as well as hook specific APIs.
|
436
398
|
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
#### Class Output Methods
|
448
|
-
|
449
|
-
Each hook type provides a **class method** `merge_outputs` that will try to intelligently merge multiple hook results, e.g. `ClaudeHooks::UserPromptSubmit.merge_outputs(output1, output2, output3)`.
|
450
|
-
|
451
|
-
| Method | Description |
|
452
|
-
|--------|-------------|
|
453
|
-
| `merge_outputs(*outputs_data)` | Intelligently merge multiple outputs into a single output |
|
454
|
-
|
455
|
-
### Configuration and Utility Methods
|
456
|
-
|
457
|
-
Available in all hooks via the base `ClaudeHooks::Base` class:
|
458
|
-
|
459
|
-
#### Utility Methods
|
460
|
-
| Method | Description |
|
461
|
-
|--------|-------------|
|
462
|
-
| `log(message, level: :info)` | Log to session-specific file (levels: :info, :warn, :error) |
|
463
|
-
|
464
|
-
#### Configuration Methods
|
465
|
-
| Method | Description |
|
466
|
-
|--------|-------------|
|
467
|
-
| `home_claude_dir` | Get the home Claude directory (`$HOME/.claude`) |
|
468
|
-
| `project_claude_dir` | Get the project Claude directory (`$CLAUDE_PROJECT_DIR/.claude`, or `nil`) |
|
469
|
-
| `home_path_for(relative_path)` | Get absolute path relative to home Claude directory |
|
470
|
-
| `project_path_for(relative_path)` | Get absolute path relative to project Claude directory (or `nil`) |
|
471
|
-
| `base_dir` | Get the base Claude directory (**deprecated**) |
|
472
|
-
| `path_for(relative_path, base_dir=nil)` | Get absolute path relative to specified or default base dir (**deprecated**) |
|
473
|
-
| `config` | Access the merged configuration object |
|
474
|
-
| `config.get_config_value(env_key, config_file_key, default)` | Get any config value with fallback |
|
475
|
-
| `config.logs_directory` | Get logs directory path (always under home directory) |
|
476
|
-
| `config.your_custom_key` | Access any custom config via method_missing |
|
477
|
-
|
478
|
-
|
479
|
-
### UserPromptSubmit API
|
480
|
-
|
481
|
-
Available when inheriting from `ClaudeHooks::UserPromptSubmit`:
|
482
|
-
|
483
|
-
#### Input Methods
|
484
|
-
| Method | Description |
|
485
|
-
|--------|-------------|
|
486
|
-
| `prompt` | Get the user's prompt text |
|
487
|
-
| `user_prompt` | Alias for `prompt` |
|
488
|
-
| `current_prompt` | Alias for `prompt` |
|
489
|
-
|
490
|
-
#### Output Methods
|
491
|
-
| Method | Description |
|
492
|
-
|--------|-------------|
|
493
|
-
| `add_additional_context!(context)` | Add context to the prompt |
|
494
|
-
| `add_context!(context)` | Alias for `add_additional_context!` |
|
495
|
-
| `empty_additional_context!` | Remove additional context |
|
496
|
-
| `block_prompt!(reason)` | Block the prompt from processing |
|
497
|
-
| `unblock_prompt!` | Unblock a previously blocked prompt |
|
498
|
-
|
499
|
-
### PreToolUse API
|
500
|
-
|
501
|
-
Available when inheriting from `ClaudeHooks::PreToolUse`:
|
502
|
-
|
503
|
-
#### Input Methods
|
504
|
-
| Method | Description |
|
505
|
-
|--------|-------------|
|
506
|
-
| `tool_name` | Get the name of the tool being used |
|
507
|
-
| `tool_input` | Get the input data for the tool |
|
508
|
-
|
509
|
-
#### Output Methods
|
510
|
-
| Method | Description |
|
511
|
-
|--------|-------------|
|
512
|
-
| `approve_tool!(reason)` | Explicitly approve tool usage |
|
513
|
-
| `block_tool!(reason)` | Block tool usage with feedback |
|
514
|
-
| `ask_for_permission!(reason)` | Request user permission |
|
515
|
-
|
516
|
-
### PostToolUse API
|
517
|
-
|
518
|
-
Available when inheriting from `ClaudeHooks::PostToolUse`:
|
519
|
-
|
520
|
-
#### Input Methods
|
521
|
-
| Method | Description |
|
522
|
-
|--------|-------------|
|
523
|
-
| `tool_name` | Get the name of the tool that was used |
|
524
|
-
| `tool_input` | Get the input that was passed to the tool |
|
525
|
-
| `tool_response` | Get the tool's response/output |
|
526
|
-
|
527
|
-
#### Output Methods
|
528
|
-
| Method | Description |
|
529
|
-
|--------|-------------|
|
530
|
-
| `block_tool!(reason)` | Block the tool result from being used |
|
531
|
-
| `approve_tool!(reason)` | Clear any previous block decision (allows tool result) |
|
532
|
-
|
533
|
-
### Notification API
|
534
|
-
|
535
|
-
Available when inheriting from `ClaudeHooks::Notification`:
|
536
|
-
|
537
|
-
#### Input Methods
|
538
|
-
| Method | Description |
|
539
|
-
|--------|-------------|
|
540
|
-
| `message` | Get the notification message content |
|
541
|
-
| `notification_message` | Alias for `message` |
|
542
|
-
|
543
|
-
#### Output Methods
|
544
|
-
Notifications are outside facing and do not have any specific output methods.
|
545
|
-
|
546
|
-
### Stop API
|
547
|
-
|
548
|
-
Available when inheriting from `ClaudeHooks::Stop`:
|
549
|
-
|
550
|
-
#### Input Methods
|
551
|
-
| Method | Description |
|
552
|
-
|--------|-------------|
|
553
|
-
| `stop_hook_active` | Check if Claude Code is already continuing as a result of a stop hook |
|
554
|
-
|
555
|
-
#### Output Methods
|
556
|
-
| Method | Description |
|
557
|
-
|--------|-------------|
|
558
|
-
| `continue_with_instructions!(instructions)` | Block Claude from stopping and provide instructions to continue |
|
559
|
-
| `block!(instructions)` | Alias for `continue_with_instructions!` |
|
560
|
-
| `ensure_stopping!` | Allow Claude to stop normally (default behavior) |
|
561
|
-
|
562
|
-
### SubagentStop API
|
563
|
-
|
564
|
-
Available when inheriting from `ClaudeHooks::SubagentStop` (inherits from `ClaudeHooks::Stop`):
|
565
|
-
|
566
|
-
#### Input Methods
|
567
|
-
| Method | Description |
|
568
|
-
|--------|-------------|
|
569
|
-
| `stop_hook_active` | Check if Claude Code is already continuing as a result of a stop hook |
|
570
|
-
|
571
|
-
#### Output Methods
|
572
|
-
| Method | Description |
|
573
|
-
|--------|-------------|
|
574
|
-
| `continue_with_instructions!(instructions)` | Block Claude from stopping and provide instructions to continue |
|
575
|
-
| `block!(instructions)` | Alias for `continue_with_instructions!` |
|
576
|
-
| `ensure_stopping!` | Allow Claude to stop normally (default behavior) |
|
577
|
-
|
578
|
-
### PreCompact API
|
579
|
-
|
580
|
-
Available when inheriting from `ClaudeHooks::PreCompact`:
|
581
|
-
|
582
|
-
#### Input Methods
|
583
|
-
| Method | Description |
|
584
|
-
|--------|-------------|
|
585
|
-
| `trigger` | Get the compaction trigger: `'manual'` or `'auto'` |
|
586
|
-
| `custom_instructions` | Get custom instructions (only available for manual trigger) |
|
587
|
-
|
588
|
-
#### Output Methods
|
589
|
-
No specific output methods are available to alter compaction behavior.
|
590
|
-
|
591
|
-
#### Utility Methods
|
592
|
-
| Method | Description |
|
593
|
-
|--------|-------------|
|
594
|
-
| `backup_transcript!(backup_file_path)` | Create a backup of the transcript at the specified path |
|
595
|
-
|
596
|
-
### SessionStart API
|
597
|
-
|
598
|
-
Available when inheriting from `ClaudeHooks::SessionStart`:
|
599
|
-
|
600
|
-
#### Input Methods
|
601
|
-
| Method | Description |
|
602
|
-
|--------|-------------|
|
603
|
-
| `source` | Get the session start source: `'startup'`, `'resume'`, or `'clear'` |
|
604
|
-
|
605
|
-
#### Output Methods
|
606
|
-
| Method | Description |
|
607
|
-
|--------|-------------|
|
608
|
-
| `add_additional_context!(context)` | Add contextual information for Claude's session |
|
609
|
-
| `add_context!(context)` | Alias for `add_additional_context!` |
|
610
|
-
| `empty_additional_context!` | Clear additional context |
|
399
|
+
- [📚 Common API Methods](docs/API/COMMON.md)
|
400
|
+
- [🔔 Notification Hooks](docs/API/NOTIFICATION.md)
|
401
|
+
- [🚀 Session Start Hooks](docs/API/SESSION_START.md)
|
402
|
+
- [🖋️ User Prompt Submit Hooks](docs/API/USER_PROMPT_SUBMIT.md)
|
403
|
+
- [🛠️ Pre-Tool Use Hooks](docs/API/PRE_TOOL_USE.md)
|
404
|
+
- [🔧 Post-Tool Use Hooks](docs/API/POST_TOOL_USE.md)
|
405
|
+
- [📝 Pre-Compact Hooks](docs/API/PRE_COMPACT.md)
|
406
|
+
- [⏹️ Stop Hooks](docs/API/STOP.md)
|
407
|
+
- [⏹️ Subagent Stop Hooks](docs/API/SUBAGENT_STOP.md)
|
408
|
+
- [🔚 Session End Hooks](docs/API/SESSION_END.md)
|
611
409
|
|
612
410
|
### 📝 Logging
|
613
411
|
|
614
|
-
`ClaudeHooks::Base` provides a **session logger** that
|
412
|
+
`ClaudeHooks::Base` provides a **session logger** to all its subclasses that you can use to write logs to session-specific files.
|
615
413
|
|
616
414
|
```ruby
|
617
415
|
log "Simple message"
|
@@ -629,7 +427,8 @@ You can also use the logger from an entrypoint script:
|
|
629
427
|
```ruby
|
630
428
|
require 'claude_hooks'
|
631
429
|
|
632
|
-
|
430
|
+
input_data = JSON.parse(STDIN.read)
|
431
|
+
logger = ClaudeHooks::Logger.new(input_data["session_id"], 'entrypoint')
|
633
432
|
logger.log "Simple message"
|
634
433
|
```
|
635
434
|
|
@@ -642,6 +441,7 @@ Logs are written to session-specific files in the configured log directory:
|
|
642
441
|
```
|
643
442
|
[2025-08-16 03:45:28] [INFO] [MyHookHandler] Starting execution
|
644
443
|
[2025-08-16 03:45:28] [ERROR] [MyHookHandler] Connection timeout
|
444
|
+
...
|
645
445
|
```
|
646
446
|
|
647
447
|
## 📝 Example: Tool usage monitor
|
@@ -666,7 +466,7 @@ First, register an entrypoint in `~/.claude/settings.json`:
|
|
666
466
|
}
|
667
467
|
```
|
668
468
|
|
669
|
-
Then, create your main entrypoint script and
|
469
|
+
Then, create your main entrypoint script and _don't forget to make it executable_:
|
670
470
|
```bash
|
671
471
|
touch ~/.claude/hooks/entrypoints/pre_tool_use.rb
|
672
472
|
chmod +x ~/.claude/hooks/entrypoints/pre_tool_use.rb
|
@@ -683,28 +483,19 @@ begin
|
|
683
483
|
input_data = JSON.parse(STDIN.read)
|
684
484
|
|
685
485
|
tool_monitor = ToolMonitor.new(input_data)
|
686
|
-
|
687
|
-
|
688
|
-
# Any other hook scripts can be chained here
|
486
|
+
tool_monitor.call
|
689
487
|
|
690
|
-
|
488
|
+
# You could also call any other handler here and then merge the outputs
|
691
489
|
|
692
|
-
|
693
|
-
log "Error parsing JSON: #{e.message}", level: :error
|
694
|
-
puts JSON.generate({
|
695
|
-
continue: false,
|
696
|
-
stopReason: "JSON parsing error: #{e.message}",
|
697
|
-
suppressOutput: false
|
698
|
-
})
|
699
|
-
exit 0
|
490
|
+
tool_monitor.output_and_exit
|
700
491
|
rescue StandardError => e
|
701
|
-
|
702
|
-
puts JSON.generate({
|
492
|
+
STDERR.puts JSON.generate({
|
703
493
|
continue: false,
|
704
494
|
stopReason: "Hook execution error: #{e.message}",
|
705
495
|
suppressOutput: false
|
706
496
|
})
|
707
|
-
|
497
|
+
# Non-blocking error
|
498
|
+
exit 1
|
708
499
|
end
|
709
500
|
```
|
710
501
|
|
@@ -727,51 +518,107 @@ class ToolMonitor < ClaudeHooks::PreToolUse
|
|
727
518
|
|
728
519
|
if DANGEROUS_TOOLS.include?(tool_name)
|
729
520
|
log "Dangerous tool detected: #{tool_name}", level: :warn
|
521
|
+
# Use one of the ClaudeHooks::PreToolUse methods to modify the hook state and block the tool
|
730
522
|
ask_for_permission!("The tool '#{tool_name}' can impact your system. Allow?")
|
731
523
|
else
|
524
|
+
# Use one of the ClaudeHooks::PreToolUse methods to modify the hook state and allow the tool
|
732
525
|
approve_tool!("Safe tool usage")
|
733
526
|
end
|
734
527
|
|
735
|
-
|
528
|
+
# Accessor provided by ClaudeHooks::PreToolUse
|
529
|
+
output
|
736
530
|
end
|
737
531
|
end
|
738
532
|
```
|
739
533
|
|
740
534
|
## 🔄 Hook Output
|
741
535
|
|
536
|
+
Hooks provide access to their output (which acts as the "state" of a hook) through the `output` method.
|
537
|
+
|
538
|
+
This method will return an output object based on the hook's type class (e.g: `ClaudeHooks::Output::UserPromptSubmit`) that provides helper methods:
|
539
|
+
- to access output data
|
540
|
+
- for merging multiple outputs
|
541
|
+
- for sending the right exit codes and output data back to Claude Code through the proper stream.
|
542
|
+
|
543
|
+
> [!TIP]
|
544
|
+
> You can also always access the raw output data hash instead of the output object using `hook.output_data`.
|
545
|
+
|
546
|
+
|
742
547
|
### 🔄 Hook Output Merging
|
743
548
|
|
744
|
-
|
549
|
+
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.
|
551
|
+
Merged outputs always inherit the **most restrictive behavior**.
|
745
552
|
|
746
553
|
```ruby
|
747
|
-
# Merge results from multiple UserPromptSubmit hooks
|
748
|
-
merged_result = ClaudeHooks::UserPromptSubmit.merge_outputs(output1, output2, output3)
|
749
554
|
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
555
|
+
require 'json'
|
556
|
+
require_relative '../handlers/user_prompt_submit/hook1'
|
557
|
+
require_relative '../handlers/user_prompt_submit/hook2'
|
558
|
+
require_relative '../handlers/user_prompt_submit/hook3'
|
559
|
+
|
560
|
+
begin
|
561
|
+
# Read input from stdin
|
562
|
+
input_data = JSON.parse(STDIN.read)
|
563
|
+
|
564
|
+
hook1 = Hook1.new(input_data)
|
565
|
+
hook2 = Hook1.new(input_data)
|
566
|
+
hook3 = Hook1.new(input_data)
|
567
|
+
|
568
|
+
# Execute the multiple hooks
|
569
|
+
hook1.call
|
570
|
+
hook2.call
|
571
|
+
hook3.call
|
572
|
+
|
573
|
+
# Merge the outputs
|
574
|
+
# In this case, ClaudeHooks::Output::UserPromptSubmit.merge follows the following merge logic:
|
575
|
+
# - continue: false wins (any hook script can stop execution)
|
576
|
+
# - suppressOutput: true wins (any hook script can suppress output)
|
577
|
+
# - decision: "block" wins (any hook script can block)
|
578
|
+
# - stopReason/reason: concatenated
|
579
|
+
# - additionalContext: concatenated
|
580
|
+
merged_output = ClaudeHooks::Output::UserPromptSubmit.merge(
|
581
|
+
hook1.output,
|
582
|
+
hook2.output,
|
583
|
+
hook3.output
|
584
|
+
)
|
585
|
+
|
586
|
+
# 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
|
588
|
+
end
|
756
589
|
```
|
757
590
|
|
758
591
|
### 🚪 Hook Exit Codes
|
759
592
|
|
760
|
-
|
593
|
+
> [!NOTE]
|
594
|
+
> Hooks and output objects handle exit codes automatically. The information below is for reference and understanding. When using `hook.output_and_exit` or `merged_output.output_and_exit`, you don't need to memorize these rules - the method chooses the correct exit code based on the hook type and the hook's state.
|
595
|
+
|
596
|
+
Claude Code hooks support multiple exit codes with different behaviors depending on the hook type.
|
761
597
|
|
762
|
-
|
763
|
-
-
|
764
|
-
- **`exit 1`**: Non-blocking error, `STDERR` will be fed back to the user
|
765
|
-
- **`exit 2`**: Blocking error, `STDERR` will be fed back to Claude
|
598
|
+
- **`exit 0`**: Success, allows the operation to continue, for most hooks, `STDOUT` will be fed back to the user.
|
599
|
+
- Claude Code does not see stdout if the exit code is 0, except for hooks where `STDOUT` is injected as context.
|
600
|
+
- **`exit 1`**: Non-blocking error, `STDERR` will be fed back to the user.
|
601
|
+
- **`exit 2`**: Blocking error, in most cases `STDERR` will be fed back to Claude.
|
602
|
+
- **Other exit codes**: Treated as non-blocking errors - `STDERR` fed back to the user, execution continues.
|
603
|
+
|
604
|
+
> [!WARNING]
|
605
|
+
> Some exit codes have different meanings depending on the hook type, here is a table to help summarize this.
|
766
606
|
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
607
|
+
| Hook Event | Exit 0 (Success) | Exit 1 (Non-blocking Error) | Exit Code 2 (Blocking Error) |
|
608
|
+
|------------------|------------------------------------------------------------|-------------------------------------------------------|----------------------------------------------------------------|
|
609
|
+
| UserPromptSubmit | Operation continues<br/><br />**`STDOUT` added as context to Claude** | Non-blocking error<br/><br />`STDERR` shown to user | **Blocks prompt processing**<br/>**Erases prompt**<br/><br />`STDERR` shown to user only |
|
610
|
+
| PreToolUse | Operation continues<br/><br />`STDOUT` shown to user in transcript mode | Non-blocking error<br/><br />`STDERR` shown to user | **Blocks the tool call**<br/><br />`STDERR` shown to Claude |
|
611
|
+
| PostToolUse | Operation continues<br/><br />`STDOUT` shown to user in transcript mode | Non-blocking error<br/><br />`STDERR` shown to user | N/A<br/><br />`STDERR` shown to Claude *(tool already ran)* |
|
612
|
+
| Notification | Operation continues<br/><br />Logged to debug only (`--debug`) | Non-blocking error<br/><br />Logged to debug only (`--debug`) | N/A<br/><br />Logged to debug only (`--debug`) |
|
613
|
+
| Stop | Agent will stop<br/><br />`STDOUT` shown to user in transcript mode | Agent will stop<br/><br />`STDERR` shown to user | **Blocks stoppage**<br/><br />`STDERR` shown to Claude |
|
614
|
+
| SubagentStop | Subagent will stop<br/><br />`STDOUT` shown to user in transcript mode | Subagent will stop<br/><br />`STDERR` shown to user | **Blocks stoppage**<br/><br />`STDERR` shown to Claude subagent |
|
615
|
+
| PreCompact | Operation continues<br/><br />`STDOUT` shown to user in transcript mode | Non-blocking error<br/><br />`STDERR` shown to user | N/A<br/><br />`STDERR` shown to user only |
|
616
|
+
| SessionStart | Operation continues<br/><br />**`STDOUT` added as context to Claude** | Non-blocking error<br/><br />`STDERR` shown to user | N/A<br/><br />`STDERR` shown to user only |
|
617
|
+
| SessionEnd | Operation continues<br/><br />Logged to debug only (`--debug`) | Non-blocking error<br/><br />Logged to debug only (`--debug`) | N/A<br/><br />Logged to debug only (`--debug`) |
|
771
618
|
|
772
619
|
|
773
|
-
|
774
|
-
For the operation to continue for a UserPromptSubmit hook, you would
|
620
|
+
#### Manually outputing and exiting example with success
|
621
|
+
For the operation to continue for a `UserPromptSubmit` hook, you would `STDOUT.puts` structured JSON data followed by `exit 0`:
|
775
622
|
|
776
623
|
```ruby
|
777
624
|
puts JSON.generate({
|
@@ -786,9 +633,8 @@ puts JSON.generate({
|
|
786
633
|
exit 0
|
787
634
|
```
|
788
635
|
|
789
|
-
|
790
|
-
|
791
|
-
For the operation to stop for a UserPromptSubmit hook, you would return structured JSON data followed by `exit 1`:
|
636
|
+
#### Manually outputing and exiting example with error
|
637
|
+
For the operation to stop for a `UserPromptSubmit` hook, you would `STDERR.puts` structured JSON data followed by `exit 2`:
|
792
638
|
|
793
639
|
```ruby
|
794
640
|
STDERR.puts JSON.generate({
|
@@ -796,19 +642,17 @@ STDERR.puts JSON.generate({
|
|
796
642
|
stopReason: "JSON parsing error: #{e.message}",
|
797
643
|
suppressOutput: false
|
798
644
|
})
|
799
|
-
exit
|
645
|
+
exit 2
|
800
646
|
```
|
801
647
|
|
802
648
|
> [!WARNING]
|
803
|
-
>
|
804
|
-
|
649
|
+
> You don't have to manually do this, just use `output_and_exit` to automatically handle this.
|
805
650
|
|
806
651
|
## 🚨 Advices
|
807
652
|
|
808
|
-
1. **Logging**: Use `log()` method instead of `puts` to avoid interfering with
|
653
|
+
1. **Logging**: Use `log()` method instead of `puts` to avoid interfering with Claude Code's expected output.
|
809
654
|
2. **Error Handling**: Hooks should handle their own errors and use the `log` method for debugging. For errors, don't forget to exit with the right exit code (1, 2) and output the JSON indicating the error to STDERR using `STDERR.puts`.
|
810
|
-
3. **
|
811
|
-
4. **Path Management**: Use `path_for()` for all file operations relative to the Claude base directory
|
655
|
+
3. **Path Management**: Use `path_for()` for all file operations relative to the Claude base directory.
|
812
656
|
|
813
657
|
## ⚠️ Troubleshooting
|
814
658
|
|
@@ -911,7 +755,7 @@ class MyTestHook < ClaudeHooks::UserPromptSubmit
|
|
911
755
|
log "All input keys: #{input_data.keys.join(', ')}"
|
912
756
|
end
|
913
757
|
|
914
|
-
|
758
|
+
output
|
915
759
|
end
|
916
760
|
end
|
917
761
|
|
@@ -929,5 +773,19 @@ end
|
|
929
773
|
|
930
774
|
```bash
|
931
775
|
# Test with sample data
|
932
|
-
echo '{"session_id": "test", "transcript_path": "/tmp/transcript", "cwd": "/tmp", "hook_event_name": "UserPromptSubmit", "user_prompt": "Hello Claude"}' | ruby ~/.claude/hooks/entrypoints/user_prompt_submit.rb
|
776
|
+
echo '{"session_id": "test", "transcript_path": "/tmp/transcript", "cwd": "/tmp", "hook_event_name": "UserPromptSubmit", "user_prompt": "Hello Claude"}' | CLAUDE_PROJECT_DIR=$(pwd) ruby ~/.claude/hooks/entrypoints/user_prompt_submit.rb
|
777
|
+
```
|
778
|
+
|
779
|
+
## 🧪 Development & Contributing
|
780
|
+
|
781
|
+
### Running Tests
|
782
|
+
|
783
|
+
This project uses Minitest for testing. To run the complete test suite:
|
784
|
+
|
785
|
+
```bash
|
786
|
+
# Run all tests
|
787
|
+
ruby test/run_all_tests.rb
|
788
|
+
|
789
|
+
# Run a specific test file
|
790
|
+
ruby test/test_output_classes.rb
|
933
791
|
```
|