claude_hooks 1.1.0 → 1.2.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/.agents/skills/ci-monitoring/SKILL.md +103 -0
- data/.agents/skills/run-tests/SKILL.md +45 -0
- data/.claude/settings.local.json +21 -0
- data/AGENTS.md +46 -0
- data/CHANGELOG.md +43 -0
- data/README.md +156 -21
- data/docs/API/COMMON.md +7 -1
- data/docs/API/CONFIG_CHANGE.md +39 -0
- data/docs/API/CWD_CHANGED.md +34 -0
- data/docs/API/ELICITATION.md +44 -0
- data/docs/API/ELICITATION_RESULT.md +43 -0
- data/docs/API/FILE_CHANGED.md +37 -0
- data/docs/API/INSTRUCTIONS_LOADED.md +30 -0
- data/docs/API/MESSAGE_DISPLAY.md +38 -0
- data/docs/API/PERMISSION_DENIED.md +37 -0
- data/docs/API/PERMISSION_REQUEST.md +11 -8
- data/docs/API/POST_COMPACT.md +30 -0
- data/docs/API/POST_TOOL_BATCH.md +39 -0
- data/docs/API/POST_TOOL_USE_FAILURE.md +31 -0
- data/docs/API/PRE_COMPACT.md +10 -2
- data/docs/API/PRE_TOOL_USE.md +5 -1
- data/docs/API/SETUP.md +37 -0
- data/docs/API/STOP_FAILURE.md +19 -0
- data/docs/API/SUBAGENT_START.md +40 -0
- data/docs/API/TASK_COMPLETED.md +43 -0
- data/docs/API/TASK_CREATED.md +43 -0
- data/docs/API/TEAMMATE_IDLE.md +40 -0
- data/docs/API/USER_PROMPT_EXPANSION.md +43 -0
- data/docs/API/WORKTREE_CREATE.md +39 -0
- data/docs/API/WORKTREE_REMOVE.md +33 -0
- data/docs/PROMPT_BASED_HOOKS.md +386 -0
- data/docs/external/claude-hooks-reference.md +2406 -854
- data/docs/mitts/ideabox.md +12 -0
- data/docs/mitts/prd.md +417 -0
- data/docs/mitts/setup.md +303 -0
- data/docs/mitts/task.md +44 -0
- data/lib/claude_hooks/base.rb +25 -0
- data/lib/claude_hooks/config_change.rb +28 -0
- data/lib/claude_hooks/cwd_changed.rb +28 -0
- data/lib/claude_hooks/elicitation.rb +61 -0
- data/lib/claude_hooks/elicitation_result.rb +57 -0
- data/lib/claude_hooks/file_changed.rb +41 -0
- data/lib/claude_hooks/instructions_loaded.rb +23 -0
- data/lib/claude_hooks/message_display.rb +58 -0
- data/lib/claude_hooks/output/base.rb +46 -1
- data/lib/claude_hooks/output/config_change.rb +47 -0
- data/lib/claude_hooks/output/cwd_changed.rb +42 -0
- data/lib/claude_hooks/output/elicitation.rb +67 -0
- data/lib/claude_hooks/output/elicitation_result.rb +63 -0
- data/lib/claude_hooks/output/file_changed.rb +42 -0
- data/lib/claude_hooks/output/instructions_loaded.rb +19 -0
- data/lib/claude_hooks/output/message_display.rb +49 -0
- data/lib/claude_hooks/output/permission_denied.rb +42 -0
- data/lib/claude_hooks/output/permission_request.rb +50 -25
- data/lib/claude_hooks/output/post_compact.rb +18 -0
- data/lib/claude_hooks/output/post_tool_batch.rb +51 -0
- data/lib/claude_hooks/output/post_tool_use.rb +12 -0
- data/lib/claude_hooks/output/post_tool_use_failure.rb +42 -0
- data/lib/claude_hooks/output/pre_compact.rb +34 -3
- data/lib/claude_hooks/output/pre_tool_use.rb +8 -2
- data/lib/claude_hooks/output/session_start.rb +30 -8
- data/lib/claude_hooks/output/setup.rb +42 -0
- data/lib/claude_hooks/output/stop.rb +15 -0
- data/lib/claude_hooks/output/stop_failure.rb +19 -0
- data/lib/claude_hooks/output/subagent_start.rb +42 -0
- data/lib/claude_hooks/output/task_completed.rb +18 -0
- data/lib/claude_hooks/output/task_created.rb +18 -0
- data/lib/claude_hooks/output/teammate_idle.rb +18 -0
- data/lib/claude_hooks/output/user_prompt_expansion.rb +51 -0
- data/lib/claude_hooks/output/worktree_create.rb +60 -0
- data/lib/claude_hooks/output/worktree_remove.rb +19 -0
- data/lib/claude_hooks/permission_denied.rb +41 -0
- data/lib/claude_hooks/permission_request.rb +16 -9
- data/lib/claude_hooks/post_compact.rb +23 -0
- data/lib/claude_hooks/post_tool_batch.rb +59 -0
- data/lib/claude_hooks/post_tool_use.rb +10 -0
- data/lib/claude_hooks/post_tool_use_failure.rb +45 -0
- data/lib/claude_hooks/pre_compact.rb +7 -0
- data/lib/claude_hooks/pre_tool_use.rb +20 -2
- data/lib/claude_hooks/session_start.rb +28 -0
- data/lib/claude_hooks/setup.rb +25 -0
- data/lib/claude_hooks/stop.rb +17 -0
- data/lib/claude_hooks/stop_failure.rb +27 -0
- data/lib/claude_hooks/subagent_start.rb +23 -0
- data/lib/claude_hooks/subagent_stop.rb +5 -1
- data/lib/claude_hooks/task_completed.rb +35 -0
- data/lib/claude_hooks/task_created.rb +35 -0
- data/lib/claude_hooks/teammate_idle.rb +23 -0
- data/lib/claude_hooks/user_prompt_expansion.rb +45 -0
- data/lib/claude_hooks/version.rb +1 -1
- data/lib/claude_hooks/worktree_create.rb +27 -0
- data/lib/claude_hooks/worktree_remove.rb +19 -0
- data/lib/claude_hooks.rb +40 -0
- metadata +71 -2
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# UserPromptExpansion API
|
|
2
|
+
|
|
3
|
+
Available when inheriting from `ClaudeHooks::UserPromptExpansion`:
|
|
4
|
+
|
|
5
|
+
Runs when a slash command or other prompt-expansion mechanism is triggered. Can block the expansion.
|
|
6
|
+
|
|
7
|
+
## Input Helpers
|
|
8
|
+
|
|
9
|
+
[📚 Shared input helpers](COMMON.md#input-helpers)
|
|
10
|
+
|
|
11
|
+
| Method | Description |
|
|
12
|
+
|--------|-------------|
|
|
13
|
+
| `expansion_type` | Type of expansion (e.g. `'slash_command'`) |
|
|
14
|
+
| `command_name` | The command name (without the `/`) |
|
|
15
|
+
| `command_args` | The arguments passed to the command |
|
|
16
|
+
| `command_source` | Where the command was defined (`'user'`, `'project'`, etc.) |
|
|
17
|
+
| `prompt` | The expanded prompt text |
|
|
18
|
+
|
|
19
|
+
## Hook State Helpers
|
|
20
|
+
|
|
21
|
+
[📚 Shared hook state methods](COMMON.md#hook-state-methods)
|
|
22
|
+
|
|
23
|
+
| Method | Description |
|
|
24
|
+
|--------|-------------|
|
|
25
|
+
| `block!(reason)` | Block the expansion with a reason |
|
|
26
|
+
| `add_additional_context!(context)` | Add additional context visible to Claude |
|
|
27
|
+
|
|
28
|
+
## Output Helpers
|
|
29
|
+
|
|
30
|
+
[📚 Shared output helpers](COMMON.md#output-helpers)
|
|
31
|
+
|
|
32
|
+
| Method | Description |
|
|
33
|
+
|--------|-------------|
|
|
34
|
+
| `output.blocked?` | Check if the expansion was blocked |
|
|
35
|
+
| `output.decision` | Get the decision (`'block'` or nil) |
|
|
36
|
+
| `output.reason` | Get the block reason |
|
|
37
|
+
|
|
38
|
+
## Hook Exit Codes
|
|
39
|
+
|
|
40
|
+
| Exit Code | Behavior |
|
|
41
|
+
|-----------|----------|
|
|
42
|
+
| `exit 0` | Expansion proceeds |
|
|
43
|
+
| `exit 2` | Blocks the expansion; `STDERR` shown to Claude |
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# WorktreeCreate API
|
|
2
|
+
|
|
3
|
+
Available when inheriting from `ClaudeHooks::WorktreeCreate`:
|
|
4
|
+
|
|
5
|
+
Runs when Claude Code is about to create a git worktree. **Special contract**: the hook must print the worktree path as the last non-empty line of stdout and exit 0. An empty or missing path is treated as failure.
|
|
6
|
+
|
|
7
|
+
## Input Helpers
|
|
8
|
+
|
|
9
|
+
[📚 Shared input helpers](COMMON.md#input-helpers)
|
|
10
|
+
|
|
11
|
+
| Method | Description |
|
|
12
|
+
|--------|-------------|
|
|
13
|
+
| `name` | The slug/name for the worktree (e.g. `'feature-x'`) |
|
|
14
|
+
|
|
15
|
+
## Hook State Helpers
|
|
16
|
+
|
|
17
|
+
[📚 Shared hook state methods](COMMON.md#hook-state-methods)
|
|
18
|
+
|
|
19
|
+
| Method | Description |
|
|
20
|
+
|--------|-------------|
|
|
21
|
+
| `worktree_path!(path)` | Set the path where the worktree will be created |
|
|
22
|
+
|
|
23
|
+
## Output Helpers
|
|
24
|
+
|
|
25
|
+
[📚 Shared output helpers](COMMON.md#output-helpers)
|
|
26
|
+
|
|
27
|
+
| Method | Description |
|
|
28
|
+
|--------|-------------|
|
|
29
|
+
| `output.worktree_path` | Get the configured worktree path |
|
|
30
|
+
|
|
31
|
+
## Hook Exit Codes
|
|
32
|
+
|
|
33
|
+
| Exit Code | Behavior |
|
|
34
|
+
|-----------|----------|
|
|
35
|
+
| `exit 0` (with path on stdout) | Worktree created at the printed path |
|
|
36
|
+
| `exit 1` / empty stdout | Creation fails |
|
|
37
|
+
|
|
38
|
+
> [!IMPORTANT]
|
|
39
|
+
> `output_and_exit` for WorktreeCreate prints the **bare path** (not JSON). Do not emit JSON for this hook type.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# WorktreeRemove API
|
|
2
|
+
|
|
3
|
+
Available when inheriting from `ClaudeHooks::WorktreeRemove`:
|
|
4
|
+
|
|
5
|
+
Runs when a git worktree is removed. Non-blocking — only `suppressOutput` is honored.
|
|
6
|
+
|
|
7
|
+
## Input Helpers
|
|
8
|
+
|
|
9
|
+
[📚 Shared input helpers](COMMON.md#input-helpers)
|
|
10
|
+
|
|
11
|
+
| Method | Description |
|
|
12
|
+
|--------|-------------|
|
|
13
|
+
| `worktree_path` | The path of the worktree being removed |
|
|
14
|
+
|
|
15
|
+
## Hook State Helpers
|
|
16
|
+
|
|
17
|
+
[📚 Shared hook state methods](COMMON.md#hook-state-methods)
|
|
18
|
+
|
|
19
|
+
| Method | Description |
|
|
20
|
+
|--------|-------------|
|
|
21
|
+
| `suppress_output!` | Hide this hook's `STDOUT` from transcript mode (the only honored output control) |
|
|
22
|
+
|
|
23
|
+
## Output Helpers
|
|
24
|
+
|
|
25
|
+
[📚 Shared output helpers](COMMON.md#output-helpers)
|
|
26
|
+
|
|
27
|
+
| Method | Description |
|
|
28
|
+
|--------|-------------|
|
|
29
|
+
| `output.suppress_output?` | Whether output is suppressed |
|
|
30
|
+
|
|
31
|
+
## Hook Exit Codes
|
|
32
|
+
|
|
33
|
+
Non-blocking. Exit code is ignored; only `suppressOutput` affects behavior.
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# Prompt-Based Hooks
|
|
2
|
+
|
|
3
|
+
Claude Code supports two types of hooks: **command hooks** and **prompt-based hooks**. This guide explains prompt-based hooks and how they differ from traditional command hooks.
|
|
4
|
+
|
|
5
|
+
## What are Prompt-Based Hooks?
|
|
6
|
+
|
|
7
|
+
Prompt-based hooks (`type: "prompt"`) delegate decision-making to an LLM (Haiku) instead of executing a bash command. When a hook event occurs, Claude Code sends your prompt and the hook input to the LLM, which responds with structured JSON containing a decision.
|
|
8
|
+
|
|
9
|
+
## How They Work
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
graph LR
|
|
13
|
+
A[Hook Event] --> B[Claude Code sends<br/>prompt + hook input<br/>to Haiku]
|
|
14
|
+
B --> C[LLM evaluates<br/>and responds<br/>with JSON]
|
|
15
|
+
C --> D[Claude Code<br/>processes decision]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Configuration
|
|
19
|
+
|
|
20
|
+
In your `.claude/settings.json`:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"hooks": {
|
|
25
|
+
"Stop": [
|
|
26
|
+
{
|
|
27
|
+
"hooks": [
|
|
28
|
+
{
|
|
29
|
+
"type": "prompt",
|
|
30
|
+
"prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete.",
|
|
31
|
+
"timeout": 30
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Fields:**
|
|
41
|
+
- `type`: Must be `"prompt"` (vs. `"command"` for bash hooks)
|
|
42
|
+
- `prompt`: The prompt text to send to the LLM
|
|
43
|
+
- Use `$ARGUMENTS` as a placeholder for the hook input JSON
|
|
44
|
+
- If `$ARGUMENTS` is not present, input JSON is appended to the prompt
|
|
45
|
+
- `timeout`: (Optional) Timeout in seconds (default: 30)
|
|
46
|
+
|
|
47
|
+
## Response Schema
|
|
48
|
+
|
|
49
|
+
The LLM must respond with JSON containing:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"decision": "approve" | "block",
|
|
54
|
+
"reason": "Explanation for the decision",
|
|
55
|
+
"continue": false, // Optional: stops Claude entirely
|
|
56
|
+
"stopReason": "Message shown to user", // Optional: custom stop message
|
|
57
|
+
"systemMessage": "Warning or context" // Optional: shown to user
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Response fields:**
|
|
62
|
+
- `decision`: `"approve"` allows the action, `"block"` prevents it
|
|
63
|
+
- `reason`: Explanation shown to Claude when decision is `"block"`
|
|
64
|
+
- `continue`: (Optional) If `false`, stops Claude's execution entirely
|
|
65
|
+
- `stopReason`: (Optional) Message shown when `continue` is false
|
|
66
|
+
- `systemMessage`: (Optional) Additional message shown to the user
|
|
67
|
+
|
|
68
|
+
## When to Use Prompt-Based Hooks
|
|
69
|
+
|
|
70
|
+
### ✅ Use Prompt-Based Hooks For:
|
|
71
|
+
- **Context-aware decisions** - When the decision requires understanding natural language or complex context
|
|
72
|
+
- **Intelligent evaluation** - Determining if a task is complete or if follow-up work is needed
|
|
73
|
+
- **Nuanced judgment** - Situations where simple rules aren't sufficient
|
|
74
|
+
- **Dynamic scenarios** - When decision criteria change based on the current context
|
|
75
|
+
|
|
76
|
+
### ❌ Use Command Hooks For:
|
|
77
|
+
- **Deterministic rules** - Simple, predictable logic (if X then Y)
|
|
78
|
+
- **Performance-critical operations** - When speed matters (command hooks are faster)
|
|
79
|
+
- **Security-sensitive checks** - When you need full control over the decision logic
|
|
80
|
+
- **Simple pattern matching** - Checking for specific strings, file patterns, etc.
|
|
81
|
+
|
|
82
|
+
## Comparison: Prompt-Based vs Command Hooks
|
|
83
|
+
|
|
84
|
+
| Feature | Command Hooks (`type: "command"`) | Prompt-Based Hooks (`type: "prompt"`) |
|
|
85
|
+
|---------|-----------------------------------|---------------------------------------|
|
|
86
|
+
| **Execution** | Runs bash script/Ruby code | Queries LLM (Haiku) |
|
|
87
|
+
| **Decision logic** | You implement in code | LLM evaluates context |
|
|
88
|
+
| **Setup complexity** | Requires script file | Configure prompt in settings |
|
|
89
|
+
| **Context awareness** | Limited to script logic | Natural language understanding |
|
|
90
|
+
| **Performance** | Fast (local execution) | Slower (API call to LLM) |
|
|
91
|
+
| **Determinism** | Fully predictable | May vary based on context |
|
|
92
|
+
| **Cost** | Free (local execution) | Uses API tokens |
|
|
93
|
+
| **Use case** | Deterministic rules | Context-aware decisions |
|
|
94
|
+
|
|
95
|
+
## Supported Hook Events
|
|
96
|
+
|
|
97
|
+
Prompt-based hooks work with any hook event, but are most useful for:
|
|
98
|
+
|
|
99
|
+
- **Stop**: Intelligently decide if Claude should continue working
|
|
100
|
+
- **SubagentStop**: Evaluate if a subagent has completed its task
|
|
101
|
+
- **UserPromptSubmit**: Validate user prompts with LLM assistance
|
|
102
|
+
- **PreToolUse**: Make context-aware permission decisions
|
|
103
|
+
- **PermissionRequest**: Intelligently allow or deny permission dialogs
|
|
104
|
+
|
|
105
|
+
## Examples
|
|
106
|
+
|
|
107
|
+
### Example 1: Intelligent Stop Hook
|
|
108
|
+
|
|
109
|
+
This prompt-based hook evaluates whether Claude should stop working by analyzing the conversation context:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"hooks": {
|
|
114
|
+
"Stop": [
|
|
115
|
+
{
|
|
116
|
+
"hooks": [
|
|
117
|
+
{
|
|
118
|
+
"type": "prompt",
|
|
119
|
+
"prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"decision\": \"approve\" or \"block\", \"reason\": \"your explanation\"}",
|
|
120
|
+
"timeout": 30
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**When to use:** When you want Claude to continue working if there are incomplete tasks or unresolved errors.
|
|
130
|
+
|
|
131
|
+
### Example 2: SubagentStop with Custom Logic
|
|
132
|
+
|
|
133
|
+
This hook evaluates whether a subagent has truly completed its assigned task:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"hooks": {
|
|
138
|
+
"SubagentStop": [
|
|
139
|
+
{
|
|
140
|
+
"hooks": [
|
|
141
|
+
{
|
|
142
|
+
"type": "prompt",
|
|
143
|
+
"prompt": "Evaluate if this subagent should stop. Input: $ARGUMENTS\n\nCheck if:\n- The subagent completed its assigned task\n- Any errors occurred that need fixing\n- Additional context gathering is needed\n\nReturn: {\"decision\": \"approve\" or \"block\", \"reason\": \"explanation\"}"
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**When to use:** When subagents might stop prematurely without fully completing their assigned work.
|
|
153
|
+
|
|
154
|
+
### Example 3: Context-Aware PreToolUse
|
|
155
|
+
|
|
156
|
+
This hook uses the LLM to make nuanced decisions about tool usage:
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"hooks": {
|
|
161
|
+
"PreToolUse": [
|
|
162
|
+
{
|
|
163
|
+
"matcher": "Bash|Write|Edit",
|
|
164
|
+
"hooks": [
|
|
165
|
+
{
|
|
166
|
+
"type": "prompt",
|
|
167
|
+
"prompt": "Evaluate this tool use for safety. Tool: $ARGUMENTS\n\nConsider:\n1. Is this a destructive operation?\n2. Does it modify critical files?\n3. Is this part of a reasonable workflow?\n\nRespond: {\"decision\": \"approve\" or \"block\", \"reason\": \"explanation\"}"
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**When to use:** When you want intelligent, context-aware safety checks beyond simple pattern matching.
|
|
177
|
+
|
|
178
|
+
## Hybrid Approach: Combining Both Hook Types
|
|
179
|
+
|
|
180
|
+
You can use both command hooks and prompt-based hooks together for the best of both worlds:
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"hooks": {
|
|
185
|
+
"PreToolUse": [
|
|
186
|
+
{
|
|
187
|
+
"matcher": "Bash",
|
|
188
|
+
"hooks": [
|
|
189
|
+
{
|
|
190
|
+
"type": "command",
|
|
191
|
+
"command": "~/.claude/hooks/entrypoints/bash_safety_check.rb"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"type": "prompt",
|
|
195
|
+
"prompt": "Review this bash command for safety: $ARGUMENTS\n\nIs this safe to execute? Respond with JSON."
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Strategy:**
|
|
205
|
+
1. **Command hook first**: Fast, deterministic checks (pattern matching, blacklists)
|
|
206
|
+
2. **Prompt hook second**: Contextual evaluation for edge cases
|
|
207
|
+
|
|
208
|
+
This gives you both speed and intelligence!
|
|
209
|
+
|
|
210
|
+
## Using This Ruby DSL with Prompt-Based Hooks
|
|
211
|
+
|
|
212
|
+
**Important:** Since prompt-based hooks are executed by Claude Code (not your script), you **don't need to use this Ruby DSL for prompt-based hooks**. The DSL is specifically for `type: "command"` hooks.
|
|
213
|
+
|
|
214
|
+
However, you can still use command hooks that leverage LLMs if you want more control:
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
#!/usr/bin/env ruby
|
|
218
|
+
require 'claude_hooks'
|
|
219
|
+
require 'anthropic' # or other LLM client
|
|
220
|
+
|
|
221
|
+
class IntelligentStopHandler < ClaudeHooks::Stop
|
|
222
|
+
def call
|
|
223
|
+
# You have full control over the LLM interaction
|
|
224
|
+
client = Anthropic::Client.new
|
|
225
|
+
|
|
226
|
+
response = client.messages(
|
|
227
|
+
model: "claude-haiku-4",
|
|
228
|
+
messages: [{
|
|
229
|
+
role: "user",
|
|
230
|
+
content: "Analyze this transcript and decide if Claude should stop: #{read_transcript}"
|
|
231
|
+
}]
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Process response and make decision
|
|
235
|
+
if should_continue?(response)
|
|
236
|
+
deny_stop!("More work needed")
|
|
237
|
+
else
|
|
238
|
+
approve_stop!("All tasks complete")
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
output
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**When to use command hooks with LLMs:**
|
|
247
|
+
- You need custom LLM interaction logic
|
|
248
|
+
- You want to use a different model or provider
|
|
249
|
+
- You need to combine LLM decisions with other logic
|
|
250
|
+
- You want full control over prompts and responses
|
|
251
|
+
|
|
252
|
+
## Best Practices
|
|
253
|
+
|
|
254
|
+
### 1. Be Specific in Prompts
|
|
255
|
+
**❌ Vague:**
|
|
256
|
+
```json
|
|
257
|
+
{
|
|
258
|
+
"prompt": "Should this continue?"
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**✅ Clear:**
|
|
263
|
+
```json
|
|
264
|
+
{
|
|
265
|
+
"prompt": "Evaluate if Claude should stop working. Context: $ARGUMENTS\n\nCheck if:\n1. All user tasks are complete\n2. No errors remain\n3. No follow-up work is needed\n\nRespond with JSON: {\"decision\": \"approve\" or \"block\", \"reason\": \"explanation\"}"
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### 2. Include Decision Criteria
|
|
270
|
+
Tell the LLM exactly what to consider:
|
|
271
|
+
- List specific factors to evaluate
|
|
272
|
+
- Provide examples of approve vs block scenarios
|
|
273
|
+
- Explain the context and purpose
|
|
274
|
+
|
|
275
|
+
### 3. Test Your Prompts
|
|
276
|
+
- Verify the LLM makes correct decisions for your use cases
|
|
277
|
+
- Test edge cases and ambiguous scenarios
|
|
278
|
+
- Adjust prompts based on results
|
|
279
|
+
|
|
280
|
+
### 4. Set Appropriate Timeouts
|
|
281
|
+
- Default is 30 seconds
|
|
282
|
+
- Increase for complex evaluations
|
|
283
|
+
- Decrease for simple checks
|
|
284
|
+
|
|
285
|
+
### 5. Use for Complex Decisions Only
|
|
286
|
+
- Bash hooks are better for simple, deterministic rules
|
|
287
|
+
- Prompt-based hooks excel at nuanced, context-aware decisions
|
|
288
|
+
- Consider the hybrid approach for best results
|
|
289
|
+
|
|
290
|
+
### 6. Consider Cost and Performance
|
|
291
|
+
- Each prompt-based hook call uses API tokens
|
|
292
|
+
- Command hooks are free and faster
|
|
293
|
+
- Use prompt hooks where the added intelligence justifies the cost
|
|
294
|
+
|
|
295
|
+
## Debugging Prompt-Based Hooks
|
|
296
|
+
|
|
297
|
+
Since prompt-based hooks are executed by Claude Code, debugging is different from command hooks:
|
|
298
|
+
|
|
299
|
+
### 1. Use `--debug` flag
|
|
300
|
+
```bash
|
|
301
|
+
claude --debug
|
|
302
|
+
```
|
|
303
|
+
This shows hook execution details in the Claude Code output.
|
|
304
|
+
|
|
305
|
+
### 2. Check Claude Code logs
|
|
306
|
+
Prompt-based hook decisions and LLM responses are logged by Claude Code.
|
|
307
|
+
|
|
308
|
+
### 3. Test your prompts separately
|
|
309
|
+
Before adding a prompt-based hook, test your prompt directly with the Claude API to ensure it responds correctly.
|
|
310
|
+
|
|
311
|
+
### 4. Start simple, then refine
|
|
312
|
+
Begin with a basic prompt, verify it works, then add complexity.
|
|
313
|
+
|
|
314
|
+
## Migration: From Command Hooks to Prompt-Based Hooks
|
|
315
|
+
|
|
316
|
+
### Before (Command Hook):
|
|
317
|
+
```ruby
|
|
318
|
+
#!/usr/bin/env ruby
|
|
319
|
+
require 'claude_hooks'
|
|
320
|
+
|
|
321
|
+
class StopEvaluator < ClaudeHooks::Stop
|
|
322
|
+
def call
|
|
323
|
+
transcript = read_transcript
|
|
324
|
+
|
|
325
|
+
# Simple pattern matching
|
|
326
|
+
if transcript.include?("all tasks complete")
|
|
327
|
+
approve_stop!("Tasks done")
|
|
328
|
+
else
|
|
329
|
+
deny_stop!("Keep working")
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
output
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Settings:**
|
|
338
|
+
```json
|
|
339
|
+
{
|
|
340
|
+
"hooks": {
|
|
341
|
+
"Stop": [{
|
|
342
|
+
"hooks": [{
|
|
343
|
+
"type": "command",
|
|
344
|
+
"command": "~/.claude/hooks/stop_evaluator.rb"
|
|
345
|
+
}]
|
|
346
|
+
}]
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### After (Prompt-Based Hook):
|
|
352
|
+
```json
|
|
353
|
+
{
|
|
354
|
+
"hooks": {
|
|
355
|
+
"Stop": [
|
|
356
|
+
{
|
|
357
|
+
"hooks": [
|
|
358
|
+
{
|
|
359
|
+
"type": "prompt",
|
|
360
|
+
"prompt": "Evaluate if Claude should stop. Context: $ARGUMENTS\n\nCheck if all user tasks are complete or if more work is needed.\n\nRespond: {\"decision\": \"approve\" or \"block\", \"reason\": \"explanation\"}"
|
|
361
|
+
}
|
|
362
|
+
]
|
|
363
|
+
}
|
|
364
|
+
]
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Benefits of migration:**
|
|
370
|
+
- No Ruby script to maintain
|
|
371
|
+
- More intelligent, context-aware decisions
|
|
372
|
+
- Simpler configuration
|
|
373
|
+
|
|
374
|
+
**Tradeoffs:**
|
|
375
|
+
- Slower (API call)
|
|
376
|
+
- Uses API tokens
|
|
377
|
+
- Less deterministic
|
|
378
|
+
|
|
379
|
+
## Summary
|
|
380
|
+
|
|
381
|
+
Prompt-based hooks are a powerful addition to Claude Code that enable intelligent, context-aware decision-making. Use them when:
|
|
382
|
+
- Simple rules aren't sufficient
|
|
383
|
+
- You need natural language understanding
|
|
384
|
+
- Context and nuance matter
|
|
385
|
+
|
|
386
|
+
For everything else, stick with fast, deterministic command hooks or use this Ruby DSL for full control!
|