@mauribadnights/clooks 0.5.1 → 0.5.2

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.
@@ -0,0 +1,236 @@
1
+ # Handlers
2
+
3
+ clooks supports three handler types: **script**, **inline**, and **llm**. Each type trades off between flexibility and performance. This guide covers how each type works, when to use it, and how to structure the output.
4
+
5
+ ## Script Handlers
6
+
7
+ Script handlers run shell commands via `sh -c`. They are the most portable option -- any language that reads stdin and writes stdout works.
8
+
9
+ ### How They Work
10
+
11
+ 1. clooks spawns a child process with the handler's `command`.
12
+ 2. The full `HookInput` JSON is piped to the process's stdin.
13
+ 3. The process writes its response to stdout.
14
+ 4. If stdout is valid JSON, it is used directly. If not, the raw text is wrapped as `{"additionalContext": "..."}`.
15
+
16
+ ### Required Fields
17
+
18
+ | Field | Type | Description |
19
+ |-------|------|-------------|
20
+ | `command` | string | Shell command to execute via `sh -c` |
21
+
22
+ ### Default Timeout
23
+
24
+ 5000ms. Override with the `timeout` field.
25
+
26
+ ### Example Configuration
27
+
28
+ ```yaml
29
+ handlers:
30
+ PreToolUse:
31
+ - id: bash-guard
32
+ type: script
33
+ command: "node ~/.clooks/hooks/bash-guard.js"
34
+ filter: "Bash"
35
+ timeout: 3000
36
+ ```
37
+
38
+ ### Example Script (Node.js)
39
+
40
+ ```javascript
41
+ #!/usr/bin/env node
42
+
43
+ // Read HookInput from stdin
44
+ let data = '';
45
+ process.stdin.on('data', chunk => { data += chunk; });
46
+ process.stdin.on('end', () => {
47
+ const input = JSON.parse(data);
48
+
49
+ // Check if the Bash command looks dangerous
50
+ const args = input.tool_input || {};
51
+ const command = args.command || '';
52
+
53
+ if (command.includes('rm -rf /')) {
54
+ // Block the tool call
55
+ console.log(JSON.stringify({
56
+ decision: 'block',
57
+ reason: 'Dangerous rm -rf command detected'
58
+ }));
59
+ } else {
60
+ // Add context for Claude
61
+ console.log(JSON.stringify({
62
+ additionalContext: `Bash command reviewed: ${command.slice(0, 80)}`
63
+ }));
64
+ }
65
+ });
66
+ ```
67
+
68
+ > **Note:** Non-zero exit codes are treated as handler failures. Stderr output is captured and included in the error message.
69
+
70
+ ## Inline Handlers
71
+
72
+ Inline handlers import an ES module in-process. There is no subprocess overhead, making them the fastest handler type.
73
+
74
+ ### How They Work
75
+
76
+ 1. clooks dynamically imports the module specified by `module`.
77
+ 2. The module's default export is called with the `HookInput` object.
78
+ 3. The return value becomes the handler output.
79
+
80
+ ### Required Fields
81
+
82
+ | Field | Type | Description |
83
+ |-------|------|-------------|
84
+ | `module` | string | Path to a `.js` or `.ts` file with a default export function |
85
+
86
+ ### Default Timeout
87
+
88
+ 5000ms. Override with the `timeout` field.
89
+
90
+ ### Example Configuration
91
+
92
+ ```yaml
93
+ handlers:
94
+ PreToolUse:
95
+ - id: context-injector
96
+ type: inline
97
+ module: ~/.clooks/hooks/context.js
98
+ filter: "Write|Edit"
99
+ ```
100
+
101
+ ### Example Module
102
+
103
+ ```typescript
104
+ // ~/.clooks/hooks/context.ts
105
+ import type { HookInput } from '@mauribadnights/clooks';
106
+
107
+ export default async function(input: HookInput) {
108
+ const toolName = input.tool_name ?? 'unknown';
109
+ const cwd = input.cwd;
110
+
111
+ // Return value becomes handler output
112
+ return {
113
+ additionalContext: `Tool ${toolName} executing in ${cwd}`
114
+ };
115
+ }
116
+ ```
117
+
118
+ > **Note:** The module must have a default export that is a function. If the export is missing or not a function, the handler fails with an error message identifying the module.
119
+
120
+ ## LLM Handlers
121
+
122
+ LLM handlers call the Anthropic Messages API with prompt templates. They require no scripts -- the prompt is defined directly in the manifest.
123
+
124
+ ### How They Work
125
+
126
+ 1. The handler's `prompt` template is rendered by replacing `$VARIABLES` with actual values.
127
+ 2. The rendered prompt is sent to the Anthropic API using the specified `model`.
128
+ 3. The response text is returned as `{"additionalContext": "..."}`.
129
+
130
+ ### Required Fields
131
+
132
+ | Field | Type | Description |
133
+ |-------|------|-------------|
134
+ | `model` | string | `claude-haiku-4-5`, `claude-sonnet-4-6`, or `claude-opus-4-6` |
135
+ | `prompt` | string | Prompt template with `$VARIABLE` interpolation |
136
+
137
+ ### Optional Fields
138
+
139
+ | Field | Type | Default | Description |
140
+ |-------|------|---------|-------------|
141
+ | `maxTokens` | number | 1024 | Maximum tokens in the API response |
142
+ | `temperature` | number | 1.0 | Sampling temperature |
143
+ | `batchGroup` | string | — | Group ID for batching multiple handlers into one API call |
144
+
145
+ ### Default Timeout
146
+
147
+ 30000ms. Override with the `timeout` field.
148
+
149
+ ### Prompt Variables
150
+
151
+ | Variable | Source |
152
+ |----------|--------|
153
+ | `$TRANSCRIPT` | Session transcript (requires `transcript` in prefetch) |
154
+ | `$GIT_STATUS` | `git status --porcelain` (requires `git_status` in prefetch) |
155
+ | `$GIT_DIFF` | `git diff --stat` (requires `git_diff` in prefetch) |
156
+ | `$ARGUMENTS` | JSON-serialized `tool_input` (PreToolUse/PostToolUse) |
157
+ | `$TOOL_NAME` | Tool name (PreToolUse/PostToolUse) |
158
+ | `$PROMPT` | User prompt text (UserPromptSubmit) |
159
+ | `$CWD` | Current working directory |
160
+
161
+ ### Example Configuration
162
+
163
+ ```yaml
164
+ prefetch:
165
+ - git_status
166
+
167
+ handlers:
168
+ PreToolUse:
169
+ - id: code-reviewer
170
+ type: llm
171
+ model: claude-haiku-4-5
172
+ prompt: |
173
+ Review this tool call for potential issues.
174
+ Tool: $TOOL_NAME
175
+ Arguments: $ARGUMENTS
176
+ Git status: $GIT_STATUS
177
+
178
+ If there is a problem, explain it briefly. Otherwise say "Looks good."
179
+ filter: "Write|Edit"
180
+ maxTokens: 256
181
+ ```
182
+
183
+ See [LLM Handlers](llm-handlers.md) for batching, cost tracking, and advanced usage.
184
+
185
+ ## Handler Output Format
186
+
187
+ Handlers communicate back to Claude Code through their output. There are two primary output shapes.
188
+
189
+ ### Adding Context
190
+
191
+ Return an `additionalContext` string to inject information into Claude's context window:
192
+
193
+ ```json
194
+ {
195
+ "additionalContext": "Information to inject into Claude's context"
196
+ }
197
+ ```
198
+
199
+ This is the most common output. Claude sees this text as additional context when deciding its next action.
200
+
201
+ ### Blocking a Tool (PreToolUse only)
202
+
203
+ PreToolUse handlers can block a tool call by returning a `decision` of `"block"`:
204
+
205
+ ```json
206
+ {
207
+ "decision": "block",
208
+ "reason": "This operation is not allowed because..."
209
+ }
210
+ ```
211
+
212
+ When a handler blocks a tool, Claude receives the reason and must find an alternative approach. Multiple handlers can run for the same event -- if any handler blocks, the tool is blocked.
213
+
214
+ ### No Output
215
+
216
+ Returning nothing (empty stdout for scripts, `undefined` for inline) is valid. The handler is recorded as successful with no output.
217
+
218
+ ## Auto-Disable
219
+
220
+ Handlers that fail repeatedly are automatically disabled to prevent cascading problems:
221
+
222
+ - After **3 consecutive failures**, the handler is marked as disabled.
223
+ - Disabled handlers are skipped on subsequent invocations (logged as auto-disabled).
224
+ - State is tracked per handler ID in memory.
225
+
226
+ To re-enable a disabled handler:
227
+
228
+ - **Edit the manifest** -- any manifest reload resets state for changed handlers.
229
+ - **Use `sessionIsolation: true`** -- state resets automatically on every `SessionStart` event.
230
+ - **Restart the daemon** -- `clooks restart` clears all in-memory state.
231
+
232
+ Check current handler status with `clooks stats`, which shows error counts and disabled state per handler.
233
+
234
+ ---
235
+
236
+ [Home](../index.md) | [Prev: Manifest](manifest.md) | [Next: LLM Handlers](llm-handlers.md)
@@ -0,0 +1,145 @@
1
+ # LLM Handlers
2
+
3
+ LLM handlers call the Anthropic Messages API directly from the manifest, with prompt templates, automatic batching, and cost tracking. This guide covers advanced usage beyond the basics in [Handlers](handlers.md).
4
+
5
+ ## Basics
6
+
7
+ LLM handlers require an Anthropic API key. Provide it in one of two ways:
8
+
9
+ 1. **Environment variable:** `ANTHROPIC_API_KEY=sk-ant-...`
10
+ 2. **Manifest setting:** `settings.anthropicApiKey: sk-ant-...`
11
+
12
+ The Anthropic SDK is lazy-loaded on the first LLM handler invocation. If the SDK is not installed, the handler fails with an actionable error message.
13
+
14
+ ### Supported Models
15
+
16
+ | Model | Best For |
17
+ |-------|----------|
18
+ | `claude-haiku-4-5` | Fast, cheap checks (guards, simple reviews) |
19
+ | `claude-sonnet-4-6` | Balanced analysis (code review, context synthesis) |
20
+ | `claude-opus-4-6` | Deep reasoning (security audits, architecture review) |
21
+
22
+ ## Prompt Templates
23
+
24
+ Prompts support `$VARIABLE` interpolation. Variables are replaced with actual values before the API call.
25
+
26
+ | Variable | Requires Prefetch | Source |
27
+ |----------|-------------------|--------|
28
+ | `$TRANSCRIPT` | Yes (`transcript`) | Session transcript, last 50KB |
29
+ | `$GIT_STATUS` | Yes (`git_status`) | `git status --porcelain` output |
30
+ | `$GIT_DIFF` | Yes (`git_diff`) | `git diff --stat` output, max 20KB |
31
+ | `$ARGUMENTS` | No | JSON-serialized `tool_input` (PreToolUse/PostToolUse) |
32
+ | `$TOOL_NAME` | No | Tool name string (PreToolUse/PostToolUse) |
33
+ | `$PROMPT` | No | User prompt text (UserPromptSubmit) |
34
+ | `$CWD` | No | Current working directory |
35
+
36
+ Variables that require prefetch will resolve to an empty string if the corresponding prefetch key is not listed in the manifest's `prefetch` array.
37
+
38
+ ```yaml
39
+ prefetch:
40
+ - transcript
41
+ - git_status
42
+
43
+ handlers:
44
+ PreToolUse:
45
+ - id: reviewer
46
+ type: llm
47
+ model: claude-haiku-4-5
48
+ prompt: |
49
+ You are reviewing a tool call in a Claude Code session.
50
+
51
+ Tool: $TOOL_NAME
52
+ Arguments: $ARGUMENTS
53
+ Working directory: $CWD
54
+ Git status: $GIT_STATUS
55
+
56
+ Flag any issues. Be concise.
57
+ ```
58
+
59
+ ## Batching
60
+
61
+ Handlers with the same `batchGroup` value that fire on the same event are combined into a single API call. This reduces latency and cost when multiple LLM handlers need to analyze the same context.
62
+
63
+ ### How It Works
64
+
65
+ 1. Handlers sharing a `batchGroup` are collected.
66
+ 2. Their prompts are rendered individually, then combined into a structured multi-task prompt.
67
+ 3. A single API call is made. The model is instructed to return a JSON object keyed by handler ID.
68
+ 4. The response is parsed and each handler receives its portion of the result.
69
+ 5. Token usage and cost are split evenly across group members.
70
+
71
+ ### Scoping
72
+
73
+ Batch groups are scoped by `session_id`. Two different Claude Code sessions firing the same batch group at the same time will produce separate API calls. There is no cross-session contamination.
74
+
75
+ ### Edge Cases
76
+
77
+ - **Single-handler groups:** If a batch group contains only one handler, it falls through to individual execution (no batching overhead).
78
+ - **Mixed models:** If handlers in a group specify different models, the first handler's model is used and a warning is logged.
79
+ - **JSON parse failure:** If the batched response is not valid JSON, all handlers in the group receive the raw text as `additionalContext`.
80
+
81
+ ### Example
82
+
83
+ ```yaml
84
+ handlers:
85
+ PreToolUse:
86
+ - id: security-check
87
+ type: llm
88
+ model: claude-haiku-4-5
89
+ prompt: |
90
+ Check this tool call for security issues:
91
+ Tool: $TOOL_NAME
92
+ Arguments: $ARGUMENTS
93
+ batchGroup: pre-tool-review
94
+ filter: "Write|Bash"
95
+
96
+ - id: style-check
97
+ type: llm
98
+ model: claude-haiku-4-5
99
+ prompt: |
100
+ Check if this tool call follows project conventions:
101
+ Tool: $TOOL_NAME
102
+ Arguments: $ARGUMENTS
103
+ batchGroup: pre-tool-review
104
+ filter: "Write|Bash"
105
+ ```
106
+
107
+ Both handlers fire on the same `PreToolUse` event with the same `batchGroup`. clooks combines them into one API call, halving latency and cost compared to two separate calls.
108
+
109
+ ## Cost Tracking
110
+
111
+ All LLM handler invocations are logged to `~/.clooks/costs.jsonl`. Each entry records the timestamp, event, handler ID, model, token usage, cost in USD, and whether the call was batched.
112
+
113
+ View cost data with:
114
+
115
+ ```bash
116
+ clooks costs
117
+ ```
118
+
119
+ ### Pricing
120
+
121
+ Pricing per million tokens (as of March 2026):
122
+
123
+ | Model | Input | Output |
124
+ |-------|-------|--------|
125
+ | `claude-haiku-4-5` | $0.80 | $4.00 |
126
+ | `claude-sonnet-4-6` | $3.00 | $15.00 |
127
+ | `claude-opus-4-6` | $15.00 | $75.00 |
128
+
129
+ For batched calls, the total token cost is split evenly across all handlers in the group.
130
+
131
+ ## Best Practices
132
+
133
+ **Use Haiku for simple checks.** Guards, keyword detection, and light reviews run well on Haiku at a fraction of the cost. Reserve Sonnet and Opus for tasks that require deeper reasoning.
134
+
135
+ **Use batchGroup to combine related analyses.** If two handlers analyze the same tool call from different angles, batching them saves an API round-trip and reduces total tokens (the shared context is sent once).
136
+
137
+ **Set maxTokens conservatively.** Most handler responses are short. Setting `maxTokens: 256` or `maxTokens: 512` prevents runaway token usage on verbose responses.
138
+
139
+ **Use filter to avoid unnecessary API calls.** An LLM handler without a filter fires on every matching event. Adding `filter: "Write|Edit"` ensures the API is only called when relevant tools are invoked.
140
+
141
+ **Prefer prefetch over inline context.** If your prompt needs git status or the transcript, add the key to `prefetch` rather than running shell commands in a script handler. Prefetched data is fetched once and shared across all handlers.
142
+
143
+ ---
144
+
145
+ [Home](../index.md) | [Prev: Handlers](handlers.md) | [Next: Filtering](filtering.md)
@@ -0,0 +1,237 @@
1
+ # Manifest Reference
2
+
3
+ The manifest file (`~/.clooks/manifest.yaml`) is the single configuration source for the clooks daemon. It defines which handlers run, when they fire, and how the daemon behaves.
4
+
5
+ ## Structure
6
+
7
+ A manifest has three top-level sections:
8
+
9
+ ```yaml
10
+ prefetch:
11
+ - transcript
12
+ - git_status
13
+ - git_diff
14
+
15
+ handlers:
16
+ EventName:
17
+ - id: handler-id
18
+ type: script|inline|llm
19
+ # ...type-specific and shared fields
20
+
21
+ settings:
22
+ port: 7890
23
+ logLevel: info
24
+ authToken: token
25
+ anthropicApiKey: sk-...
26
+ ```
27
+
28
+ All sections are optional. A minimal manifest needs only a `handlers` block with at least one event and one handler.
29
+
30
+ ## Settings
31
+
32
+ Global daemon configuration lives under `settings`:
33
+
34
+ | Field | Type | Default | Description |
35
+ |-------|------|---------|-------------|
36
+ | `port` | number | 7890 | HTTP server port (1-65535) |
37
+ | `logLevel` | string | `"info"` | Log verbosity: `debug`, `info`, `warn`, `error` |
38
+ | `authToken` | string | — | Bearer token for HTTP authentication. Auto-generated by `clooks init` |
39
+ | `anthropicApiKey` | string | — | Anthropic API key. Alternative to setting the `ANTHROPIC_API_KEY` env var |
40
+
41
+ ```yaml
42
+ settings:
43
+ port: 7890
44
+ logLevel: info
45
+ authToken: clooks_a1b2c3d4e5f6
46
+ ```
47
+
48
+ ## Prefetch
49
+
50
+ The `prefetch` array declares context that should be fetched once per hook invocation and shared across all handlers. This avoids redundant work when multiple handlers need the same data.
51
+
52
+ | Key | Description | Size Limit |
53
+ |-----|-------------|------------|
54
+ | `transcript` | Last portion of the session transcript file | 50KB (tail) |
55
+ | `git_status` | Output of `git status --porcelain` in the session's cwd | — |
56
+ | `git_diff` | Output of `git diff --no-ext-diff --stat` in the session's cwd | 20KB (truncated) |
57
+
58
+ Prefetched data is available to LLM handler prompts as template variables: `$TRANSCRIPT`, `$GIT_STATUS`, `$GIT_DIFF`. Each key is fetched independently — a failure in one does not block the others.
59
+
60
+ ```yaml
61
+ prefetch:
62
+ - transcript
63
+ - git_status
64
+ ```
65
+
66
+ ## Handler Events
67
+
68
+ Handlers are grouped by the Claude Code hook event they respond to. clooks supports all 9 events:
69
+
70
+ | Event | When It Fires |
71
+ |-------|---------------|
72
+ | `SessionStart` | A new Claude Code session begins |
73
+ | `UserPromptSubmit` | The user submits a prompt |
74
+ | `PreToolUse` | Before Claude executes a tool |
75
+ | `PostToolUse` | After a tool finishes executing |
76
+ | `Stop` | Claude finishes its response |
77
+ | `SubagentStart` | A subagent session starts |
78
+ | `SubagentStop` | A subagent session ends |
79
+ | `Notification` | Claude sends a notification |
80
+ | `ConfigChange` | Claude Code configuration changes |
81
+
82
+ See [Hook Events Reference](../reference/hook-events.md) for full payload documentation per event.
83
+
84
+ Each event key contains an array of handler definitions:
85
+
86
+ ```yaml
87
+ handlers:
88
+ PreToolUse:
89
+ - id: first-handler
90
+ type: script
91
+ command: "node ~/hooks/check.js"
92
+ - id: second-handler
93
+ type: llm
94
+ model: claude-haiku-4-5
95
+ prompt: "Review this tool call: $TOOL_NAME"
96
+ ```
97
+
98
+ ## Shared Handler Fields
99
+
100
+ Every handler, regardless of type, supports these fields:
101
+
102
+ | Field | Type | Default | Description |
103
+ |-------|------|---------|-------------|
104
+ | `id` | string | required | Unique identifier across the entire manifest |
105
+ | `type` | string | required | `script`, `inline`, or `llm` |
106
+ | `enabled` | boolean | `true` | Toggle handler without removing it from the manifest |
107
+ | `filter` | string | — | Keyword filter applied to hook input. See [Filtering](filtering.md) |
108
+ | `timeout` | number | 5000 / 30000 | Milliseconds before handler is killed. Default is 5000ms for script/inline, 30000ms for LLM |
109
+ | `async` | boolean | `false` | Fire-and-forget execution. See [Async Handlers](async-handlers.md) |
110
+ | `depends` | string[] | — | Handler IDs this handler must wait for. See [Dependencies](dependencies.md) |
111
+ | `sessionIsolation` | boolean | `false` | Reset handler state (error counts, auto-disable) on every `SessionStart` |
112
+ | `agent` | string | — | Comma-separated agent names. Only fires in matching sessions. See [Filtering](filtering.md) |
113
+ | `project` | string | — | Glob pattern matched against session cwd. See [Filtering](filtering.md) |
114
+
115
+ Type-specific fields are covered in [Handlers](handlers.md).
116
+
117
+ ## Hot Reload
118
+
119
+ The daemon watches `manifest.yaml` for filesystem changes. When the file is saved:
120
+
121
+ 1. The new manifest is parsed and validated.
122
+ 2. Handler lists are diffed against the previous version.
123
+ 3. State for removed handlers is cleaned up.
124
+ 4. Handlers with `sessionIsolation: true` that changed are reset.
125
+ 5. The new handler set becomes active immediately.
126
+
127
+ No daemon restart is needed. Invalid manifests are rejected and the previous configuration continues running. Changes are logged at the `info` level.
128
+
129
+ ## Validation
130
+
131
+ Run `clooks doctor` to validate your manifest structure. The validator enforces:
132
+
133
+ - The `handlers` key must be an object.
134
+ - Each event key must be a valid hook event name (`SessionStart`, `PreToolUse`, etc.).
135
+ - Each event must contain an array of handler objects.
136
+ - Every handler must have a string `id` and a valid `type`.
137
+ - Handler IDs must be unique across the entire manifest (not just within an event).
138
+ - Script handlers must have a `command` field.
139
+ - Inline handlers must have a `module` field.
140
+ - LLM handlers must have both `model` and `prompt` fields. Model must be one of: `claude-haiku-4-5`, `claude-sonnet-4-6`, `claude-opus-4-6`.
141
+ - `prefetch` must be an array containing only valid keys: `transcript`, `git_status`, `git_diff`.
142
+ - `settings.port` must be a number between 1 and 65535.
143
+ - `settings.logLevel` must be one of: `debug`, `info`, `warn`, `error`.
144
+
145
+ Invalid manifests produce specific error messages identifying the problem. For example:
146
+
147
+ ```
148
+ Error: Unknown hook event: "PreTool". Valid events: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, SubagentStart, SubagentStop, Notification, ConfigChange
149
+ ```
150
+
151
+ ```
152
+ Error: Duplicate handler id: "my-guard"
153
+ ```
154
+
155
+ ## Complete Example
156
+
157
+ ```yaml
158
+ prefetch:
159
+ - transcript
160
+ - git_status
161
+ - git_diff
162
+
163
+ handlers:
164
+ SessionStart:
165
+ - id: check-update
166
+ type: script
167
+ command: "node ~/.clooks/hooks/check-update.js"
168
+ timeout: 6000
169
+
170
+ UserPromptSubmit:
171
+ - id: prompt-logger
172
+ type: inline
173
+ module: ~/.clooks/hooks/log-prompt.js
174
+ async: true
175
+
176
+ PreToolUse:
177
+ - id: bash-guard
178
+ type: script
179
+ command: "node ~/.clooks/hooks/bash-guard.js"
180
+ filter: "Bash"
181
+ timeout: 3000
182
+
183
+ - id: write-review
184
+ type: llm
185
+ model: claude-haiku-4-5
186
+ prompt: |
187
+ Review this file write for potential issues:
188
+ Tool: $TOOL_NAME
189
+ Arguments: $ARGUMENTS
190
+ Git status: $GIT_STATUS
191
+ filter: "Write"
192
+ batchGroup: code-review
193
+
194
+ - id: style-check
195
+ type: llm
196
+ model: claude-haiku-4-5
197
+ prompt: |
198
+ Check if this write follows project style conventions:
199
+ $ARGUMENTS
200
+ filter: "Write"
201
+ batchGroup: code-review
202
+ depends: []
203
+
204
+ - id: deep-analysis
205
+ type: llm
206
+ model: claude-sonnet-4-6
207
+ prompt: |
208
+ Perform deep security and correctness review:
209
+ $ARGUMENTS
210
+ Context from prior checks available in input.
211
+ depends: [write-review, style-check]
212
+ filter: "Write"
213
+
214
+ PostToolUse:
215
+ - id: metrics-collector
216
+ type: inline
217
+ module: ~/.clooks/hooks/metrics.js
218
+ async: true
219
+
220
+ Stop:
221
+ - id: session-summary
222
+ type: llm
223
+ model: claude-haiku-4-5
224
+ prompt: |
225
+ Summarize what was accomplished in this session:
226
+ $TRANSCRIPT
227
+ maxTokens: 512
228
+
229
+ settings:
230
+ port: 7890
231
+ logLevel: info
232
+ authToken: clooks_a1b2c3d4e5f6
233
+ ```
234
+
235
+ ---
236
+
237
+ [Home](../index.md) | [Prev: Migration](../getting-started/migration.md) | [Next: Handlers](handlers.md)
@@ -0,0 +1,31 @@
1
+ # Short-Circuit
2
+
3
+ ## Overview
4
+
5
+ When a PreToolUse handler blocks a tool call (returns `decision: "block"`), clooks automatically skips PostToolUse handlers for that same tool. This prevents wasted execution on tools that were already denied.
6
+
7
+ ## How It Works
8
+
9
+ 1. PreToolUse handler returns `{ decision: "block", reason: "..." }`
10
+ 2. Server records denial in an in-memory DenyCache, keyed by `session_id:tool_name`
11
+ 3. When PostToolUse fires for the same session + tool, clooks checks the DenyCache
12
+ 4. If denied: PostToolUse handlers are skipped entirely
13
+ 5. Cache entries expire after 30 seconds (prevents memory leaks)
14
+
15
+ ## Configuration
16
+
17
+ This is automatic — no configuration needed. It works for all PreToolUse handlers that return a `block` decision.
18
+
19
+ The server also checks for `hookSpecificOutput.permissionDecision: "deny"` as an alternative denial signal.
20
+
21
+ ## Cache Lifecycle
22
+
23
+ - Entries auto-expire after 30 seconds
24
+ - Periodic cleanup runs every 60 seconds
25
+ - Cache is in-memory only — cleared on daemon restart
26
+
27
+ > **Note:** The 30-second TTL is deliberately short. It only needs to survive long enough for the matching PostToolUse event to arrive, which typically happens within milliseconds of the PreToolUse denial.
28
+
29
+ ---
30
+
31
+ [Home](../index.md) | [Prev: Async Handlers](async-handlers.md) | [Next: System Service](system-service.md)