claude-flow 2.0.0-alpha.26 → 2.0.0-alpha.27
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.
- package/bin/claude-flow +1 -1
- package/package.json +1 -1
- package/src/cli/command-registry.js +33 -0
- package/src/cli/simple-commands/hook-safety.js +668 -0
- package/src/cli/simple-commands/init/templates/claude-flow-universal +1 -1
- package/src/cli/simple-commands/init/templates/safe-hook-patterns.js +381 -0
package/bin/claude-flow
CHANGED
package/package.json
CHANGED
|
@@ -18,6 +18,7 @@ import { analysisAction } from './simple-commands/analysis.js';
|
|
|
18
18
|
import { automationAction } from './simple-commands/automation.js';
|
|
19
19
|
import { coordinationAction } from './simple-commands/coordination.js';
|
|
20
20
|
import { hooksAction } from './simple-commands/hooks.js';
|
|
21
|
+
import { hookSafetyCommand } from './simple-commands/hook-safety.js';
|
|
21
22
|
import { hiveMindCommand } from './simple-commands/hive-mind.js';
|
|
22
23
|
import { showUnifiedMetrics, fixTaskAttribution } from './simple-commands/swarm-metrics-integration.js';
|
|
23
24
|
// Note: TypeScript imports commented out for Node.js compatibility
|
|
@@ -400,6 +401,38 @@ Hooks commands:
|
|
|
400
401
|
Enables automated preparation & cleanup, performance tracking, and coordination synchronization.`
|
|
401
402
|
});
|
|
402
403
|
|
|
404
|
+
commandRegistry.set('hook-safety', {
|
|
405
|
+
handler: hookSafetyCommand,
|
|
406
|
+
description: '🚨 Critical hook safety system - Prevent infinite loops & financial damage',
|
|
407
|
+
usage: 'hook-safety <command> [options]',
|
|
408
|
+
examples: [
|
|
409
|
+
'hook-safety validate # Check for dangerous hook configurations',
|
|
410
|
+
'hook-safety validate --config ~/.claude/settings.json',
|
|
411
|
+
'hook-safety status # View safety status and context',
|
|
412
|
+
'hook-safety reset # Reset circuit breakers',
|
|
413
|
+
'hook-safety safe-mode # Enable safe mode (skip all hooks)'
|
|
414
|
+
],
|
|
415
|
+
details: `
|
|
416
|
+
🚨 CRITICAL: Stop hooks calling 'claude' commands create INFINITE LOOPS that can:
|
|
417
|
+
• Bypass API rate limits
|
|
418
|
+
• Cost thousands of dollars per day
|
|
419
|
+
• Make your system unresponsive
|
|
420
|
+
|
|
421
|
+
Hook Safety commands:
|
|
422
|
+
• validate: Check Claude Code settings for dangerous patterns
|
|
423
|
+
• status: Show current safety status and execution context
|
|
424
|
+
• reset: Reset circuit breakers and execution counters
|
|
425
|
+
• safe-mode: Enable/disable safe mode (skips all hooks)
|
|
426
|
+
|
|
427
|
+
SAFE ALTERNATIVES:
|
|
428
|
+
• Use PostToolUse hooks instead of Stop hooks
|
|
429
|
+
• Implement flag-based update patterns
|
|
430
|
+
• Use 'claude --skip-hooks' for manual updates
|
|
431
|
+
• Create conditional execution scripts
|
|
432
|
+
|
|
433
|
+
For more information: https://github.com/ruvnet/claude-flow/issues/166`
|
|
434
|
+
});
|
|
435
|
+
|
|
403
436
|
commandRegistry.set('hive', {
|
|
404
437
|
handler: async (args, flags) => {
|
|
405
438
|
try {
|
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook Safety System - Prevents recursive hook execution and financial damage
|
|
3
|
+
*
|
|
4
|
+
* This system protects against infinite loops where Claude Code hooks call
|
|
5
|
+
* 'claude' commands, which could bypass rate limits and cost thousands of dollars.
|
|
6
|
+
*
|
|
7
|
+
* Critical protections:
|
|
8
|
+
* - Environment variable context detection
|
|
9
|
+
* - Recursive call prevention
|
|
10
|
+
* - Circuit breaker for Stop hooks
|
|
11
|
+
* - Configuration validation
|
|
12
|
+
* - Emergency override flags
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { printError, printWarning, printSuccess } from '../utils.js';
|
|
16
|
+
import { existsSync, readFileSync } from 'fs';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Hook Safety Configuration
|
|
21
|
+
*/
|
|
22
|
+
const HOOK_SAFETY_CONFIG = {
|
|
23
|
+
// Maximum hook execution depth before blocking
|
|
24
|
+
MAX_HOOK_DEPTH: 3,
|
|
25
|
+
|
|
26
|
+
// Maximum Stop hook executions per session
|
|
27
|
+
MAX_STOP_HOOK_EXECUTIONS: 2,
|
|
28
|
+
|
|
29
|
+
// Circuit breaker timeout (milliseconds)
|
|
30
|
+
CIRCUIT_BREAKER_TIMEOUT: 60000, // 1 minute
|
|
31
|
+
|
|
32
|
+
// Environment variables for context detection
|
|
33
|
+
ENV_VARS: {
|
|
34
|
+
CONTEXT: 'CLAUDE_HOOK_CONTEXT',
|
|
35
|
+
DEPTH: 'CLAUDE_HOOK_DEPTH',
|
|
36
|
+
SESSION_ID: 'CLAUDE_HOOK_SESSION_ID',
|
|
37
|
+
SKIP_HOOKS: 'CLAUDE_SKIP_HOOKS',
|
|
38
|
+
SAFE_MODE: 'CLAUDE_SAFE_MODE'
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Global hook execution tracking
|
|
44
|
+
*/
|
|
45
|
+
class HookExecutionTracker {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.executions = new Map();
|
|
48
|
+
this.sessionId = this.generateSessionId();
|
|
49
|
+
this.resetTimeout = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
generateSessionId() {
|
|
53
|
+
return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
track(hookType) {
|
|
57
|
+
const key = `${this.sessionId}:${hookType}`;
|
|
58
|
+
const count = this.executions.get(key) || 0;
|
|
59
|
+
this.executions.set(key, count + 1);
|
|
60
|
+
|
|
61
|
+
// Auto-reset after timeout
|
|
62
|
+
if (this.resetTimeout) clearTimeout(this.resetTimeout);
|
|
63
|
+
this.resetTimeout = setTimeout(() => {
|
|
64
|
+
this.executions.clear();
|
|
65
|
+
}, HOOK_SAFETY_CONFIG.CIRCUIT_BREAKER_TIMEOUT);
|
|
66
|
+
|
|
67
|
+
return count + 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getExecutionCount(hookType) {
|
|
71
|
+
const key = `${this.sessionId}:${hookType}`;
|
|
72
|
+
return this.executions.get(key) || 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
reset() {
|
|
76
|
+
this.executions.clear();
|
|
77
|
+
this.sessionId = this.generateSessionId();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Global instance
|
|
82
|
+
const executionTracker = new HookExecutionTracker();
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Hook Context Manager - Tracks hook execution context
|
|
86
|
+
*/
|
|
87
|
+
export class HookContextManager {
|
|
88
|
+
static setContext(hookType, depth = 1) {
|
|
89
|
+
process.env[HOOK_SAFETY_CONFIG.ENV_VARS.CONTEXT] = hookType;
|
|
90
|
+
process.env[HOOK_SAFETY_CONFIG.ENV_VARS.DEPTH] = depth.toString();
|
|
91
|
+
process.env[HOOK_SAFETY_CONFIG.ENV_VARS.SESSION_ID] = executionTracker.sessionId;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static getContext() {
|
|
95
|
+
return {
|
|
96
|
+
type: process.env[HOOK_SAFETY_CONFIG.ENV_VARS.CONTEXT],
|
|
97
|
+
depth: parseInt(process.env[HOOK_SAFETY_CONFIG.ENV_VARS.DEPTH] || '0'),
|
|
98
|
+
sessionId: process.env[HOOK_SAFETY_CONFIG.ENV_VARS.SESSION_ID],
|
|
99
|
+
skipHooks: process.env[HOOK_SAFETY_CONFIG.ENV_VARS.SKIP_HOOKS] === 'true',
|
|
100
|
+
safeMode: process.env[HOOK_SAFETY_CONFIG.ENV_VARS.SAFE_MODE] === 'true'
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static clearContext() {
|
|
105
|
+
delete process.env[HOOK_SAFETY_CONFIG.ENV_VARS.CONTEXT];
|
|
106
|
+
delete process.env[HOOK_SAFETY_CONFIG.ENV_VARS.DEPTH];
|
|
107
|
+
delete process.env[HOOK_SAFETY_CONFIG.ENV_VARS.SESSION_ID];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static isInHookContext() {
|
|
111
|
+
return !!process.env[HOOK_SAFETY_CONFIG.ENV_VARS.CONTEXT];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static setSafeMode(enabled = true) {
|
|
115
|
+
if (enabled) {
|
|
116
|
+
process.env[HOOK_SAFETY_CONFIG.ENV_VARS.SAFE_MODE] = 'true';
|
|
117
|
+
} else {
|
|
118
|
+
delete process.env[HOOK_SAFETY_CONFIG.ENV_VARS.SAFE_MODE];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static setSkipHooks(enabled = true) {
|
|
123
|
+
if (enabled) {
|
|
124
|
+
process.env[HOOK_SAFETY_CONFIG.ENV_VARS.SKIP_HOOKS] = 'true';
|
|
125
|
+
} else {
|
|
126
|
+
delete process.env[HOOK_SAFETY_CONFIG.ENV_VARS.SKIP_HOOKS];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Command Validator - Validates commands for hook safety
|
|
133
|
+
*/
|
|
134
|
+
export class HookCommandValidator {
|
|
135
|
+
/**
|
|
136
|
+
* Validate if a command is safe to execute from a hook
|
|
137
|
+
*/
|
|
138
|
+
static validateCommand(command, hookType) {
|
|
139
|
+
const context = HookContextManager.getContext();
|
|
140
|
+
const warnings = [];
|
|
141
|
+
const errors = [];
|
|
142
|
+
|
|
143
|
+
// Critical check: Claude commands in Stop hooks
|
|
144
|
+
if (hookType === 'Stop' && this.isClaudeCommand(command)) {
|
|
145
|
+
errors.push({
|
|
146
|
+
type: 'CRITICAL_RECURSION_RISK',
|
|
147
|
+
message: '🚨 CRITICAL ERROR: Claude command detected in Stop hook!\n' +
|
|
148
|
+
'This creates an INFINITE LOOP that can cost THOUSANDS OF DOLLARS.\n' +
|
|
149
|
+
'Stop hooks that call "claude" commands bypass rate limits and\n' +
|
|
150
|
+
'can result in massive unexpected API charges.\n\n' +
|
|
151
|
+
'BLOCKED FOR SAFETY - Use alternative patterns instead.'
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// General recursion detection
|
|
156
|
+
if (context.type && this.isClaudeCommand(command)) {
|
|
157
|
+
const depth = context.depth;
|
|
158
|
+
|
|
159
|
+
if (depth >= HOOK_SAFETY_CONFIG.MAX_HOOK_DEPTH) {
|
|
160
|
+
errors.push({
|
|
161
|
+
type: 'HOOK_RECURSION_LIMIT',
|
|
162
|
+
message: `🚨 Hook recursion limit exceeded! (Depth: ${depth})\n` +
|
|
163
|
+
`Hook type: ${context.type}\n` +
|
|
164
|
+
'Blocking execution to prevent infinite loop.'
|
|
165
|
+
});
|
|
166
|
+
} else {
|
|
167
|
+
warnings.push({
|
|
168
|
+
type: 'POTENTIAL_RECURSION',
|
|
169
|
+
message: `⚠️ WARNING: Claude command in ${context.type} hook (depth: ${depth})\n` +
|
|
170
|
+
'This could create recursion. Consider using --skip-hooks flag.'
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check for other dangerous patterns
|
|
176
|
+
if (this.isDangerousPattern(command, hookType)) {
|
|
177
|
+
warnings.push({
|
|
178
|
+
type: 'DANGEROUS_PATTERN',
|
|
179
|
+
message: `⚠️ WARNING: Potentially dangerous hook pattern detected.\n` +
|
|
180
|
+
'Review the command and consider safer alternatives.'
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { warnings, errors, safe: errors.length === 0 };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
static isClaudeCommand(command) {
|
|
188
|
+
// Match various forms of claude command invocation
|
|
189
|
+
const claudePatterns = [
|
|
190
|
+
/\bclaude\b/, // Direct claude command
|
|
191
|
+
/claude-code\b/, // claude-code command
|
|
192
|
+
/npx\s+claude\b/, // NPX claude
|
|
193
|
+
/\.\/claude\b/, // Local claude wrapper
|
|
194
|
+
/claude\.exe\b/ // Windows executable
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
return claudePatterns.some(pattern => pattern.test(command));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
static isDangerousPattern(command, hookType) {
|
|
201
|
+
const dangerousPatterns = [
|
|
202
|
+
// Commands that could trigger more hooks
|
|
203
|
+
/git\s+commit.*--all/,
|
|
204
|
+
/git\s+add\s+\./,
|
|
205
|
+
// File operations that might trigger watchers
|
|
206
|
+
/watch\s+.*claude/,
|
|
207
|
+
/nodemon.*claude/,
|
|
208
|
+
// Recursive script execution
|
|
209
|
+
/bash.*hook/,
|
|
210
|
+
/sh.*hook/
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
return dangerousPatterns.some(pattern => pattern.test(command));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Circuit Breaker - Prevents runaway hook execution
|
|
219
|
+
*/
|
|
220
|
+
export class HookCircuitBreaker {
|
|
221
|
+
/**
|
|
222
|
+
* Check if hook execution should be allowed
|
|
223
|
+
*/
|
|
224
|
+
static checkExecution(hookType) {
|
|
225
|
+
const executionCount = executionTracker.track(hookType);
|
|
226
|
+
|
|
227
|
+
// Stop hook protection - maximum 2 executions per session
|
|
228
|
+
if (hookType === 'Stop' && executionCount > HOOK_SAFETY_CONFIG.MAX_STOP_HOOK_EXECUTIONS) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
`🚨 CIRCUIT BREAKER ACTIVATED!\n` +
|
|
231
|
+
`Stop hook has executed ${executionCount} times in this session.\n` +
|
|
232
|
+
`This indicates a potential infinite loop that could cost thousands of dollars.\n` +
|
|
233
|
+
`Execution blocked for financial protection.\n\n` +
|
|
234
|
+
`To reset: Use --reset-circuit-breaker flag or restart your session.`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// General protection for any hook type
|
|
239
|
+
if (executionCount > 20) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`🚨 CIRCUIT BREAKER: ${hookType} hook executed ${executionCount} times!\n` +
|
|
242
|
+
`This is highly unusual and indicates a potential problem.\n` +
|
|
243
|
+
`Execution blocked to prevent system overload.`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Log warnings for concerning patterns
|
|
248
|
+
if (hookType === 'Stop' && executionCount > 1) {
|
|
249
|
+
printWarning(`⚠️ Stop hook execution #${executionCount} detected. Monitor for recursion.`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
static reset() {
|
|
256
|
+
executionTracker.reset();
|
|
257
|
+
printSuccess('Circuit breaker reset successfully.');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
static getStatus() {
|
|
261
|
+
return {
|
|
262
|
+
sessionId: executionTracker.sessionId,
|
|
263
|
+
executions: Array.from(executionTracker.executions.entries()).map(([key, count]) => {
|
|
264
|
+
const [sessionId, hookType] = key.split(':');
|
|
265
|
+
return { hookType, count };
|
|
266
|
+
})
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Configuration Validator - Validates hook configurations for safety
|
|
273
|
+
*/
|
|
274
|
+
export class HookConfigValidator {
|
|
275
|
+
/**
|
|
276
|
+
* Validate Claude Code settings.json for dangerous hook configurations
|
|
277
|
+
*/
|
|
278
|
+
static validateClaudeCodeConfig(configPath = null) {
|
|
279
|
+
if (!configPath) {
|
|
280
|
+
// Try to find Claude Code settings
|
|
281
|
+
const possiblePaths = [
|
|
282
|
+
path.join(process.env.HOME || '.', '.claude', 'settings.json'),
|
|
283
|
+
path.join(process.cwd(), '.claude', 'settings.json'),
|
|
284
|
+
path.join(process.cwd(), 'settings.json')
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
configPath = possiblePaths.find(p => existsSync(p));
|
|
288
|
+
|
|
289
|
+
if (!configPath) {
|
|
290
|
+
return { safe: true, message: 'No Claude Code configuration found.' };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
296
|
+
const validation = this.validateHooksConfig(config.hooks || {});
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
safe: validation.errors.length === 0,
|
|
300
|
+
configPath,
|
|
301
|
+
...validation
|
|
302
|
+
};
|
|
303
|
+
} catch (err) {
|
|
304
|
+
return {
|
|
305
|
+
safe: false,
|
|
306
|
+
error: `Failed to validate configuration: ${err.message}`,
|
|
307
|
+
configPath
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Validate hooks configuration object
|
|
314
|
+
*/
|
|
315
|
+
static validateHooksConfig(hooksConfig) {
|
|
316
|
+
const warnings = [];
|
|
317
|
+
const errors = [];
|
|
318
|
+
|
|
319
|
+
// Check Stop hooks specifically
|
|
320
|
+
if (hooksConfig.Stop) {
|
|
321
|
+
for (const hookGroup of hooksConfig.Stop) {
|
|
322
|
+
for (const hook of hookGroup.hooks || []) {
|
|
323
|
+
if (hook.type === 'command' && hook.command) {
|
|
324
|
+
const result = HookCommandValidator.validateCommand(hook.command, 'Stop');
|
|
325
|
+
warnings.push(...result.warnings);
|
|
326
|
+
errors.push(...result.errors);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Check other dangerous hook types
|
|
333
|
+
const dangerousHookTypes = ['SubagentStop', 'PostToolUse'];
|
|
334
|
+
for (const hookType of dangerousHookTypes) {
|
|
335
|
+
if (hooksConfig[hookType]) {
|
|
336
|
+
for (const hookGroup of hooksConfig[hookType]) {
|
|
337
|
+
for (const hook of hookGroup.hooks || []) {
|
|
338
|
+
if (hook.type === 'command' && hook.command) {
|
|
339
|
+
const result = HookCommandValidator.validateCommand(hook.command, hookType);
|
|
340
|
+
warnings.push(...result.warnings);
|
|
341
|
+
errors.push(...result.errors);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return { warnings, errors };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Generate safe configuration recommendations
|
|
353
|
+
*/
|
|
354
|
+
static generateSafeAlternatives(dangerousConfig) {
|
|
355
|
+
const alternatives = [];
|
|
356
|
+
|
|
357
|
+
// Example: Stop hook calling claude
|
|
358
|
+
if (dangerousConfig.includes('claude')) {
|
|
359
|
+
alternatives.push({
|
|
360
|
+
pattern: 'Stop hook with claude command',
|
|
361
|
+
problem: 'Creates infinite recursion loop',
|
|
362
|
+
solution: 'Use flag-based approach instead',
|
|
363
|
+
example: `
|
|
364
|
+
// Instead of this DANGEROUS pattern:
|
|
365
|
+
{
|
|
366
|
+
"Stop": [{
|
|
367
|
+
"hooks": [{"type": "command", "command": "claude -c -p 'Update history'"}]
|
|
368
|
+
}]
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Use this SAFE pattern:
|
|
372
|
+
{
|
|
373
|
+
"Stop": [{
|
|
374
|
+
"hooks": [{"type": "command", "command": "touch ~/.claude/needs_update"}]
|
|
375
|
+
}]
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Then manually run: claude -c -p "Update history" when needed
|
|
379
|
+
`
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
alternatives.push({
|
|
383
|
+
pattern: 'PostToolUse hook alternative',
|
|
384
|
+
problem: 'Stop hooks execute too frequently',
|
|
385
|
+
solution: 'Use PostToolUse for specific tools',
|
|
386
|
+
example: `
|
|
387
|
+
// SAFER: Use PostToolUse for specific operations
|
|
388
|
+
{
|
|
389
|
+
"PostToolUse": [{
|
|
390
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
391
|
+
"hooks": [{"type": "command", "command": "echo 'File modified' >> ~/.claude/changes.log"}]
|
|
392
|
+
}]
|
|
393
|
+
}
|
|
394
|
+
`
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return alternatives;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Safe Hook Execution Wrapper
|
|
404
|
+
*/
|
|
405
|
+
export class SafeHookExecutor {
|
|
406
|
+
/**
|
|
407
|
+
* Safely execute a hook command with all safety checks
|
|
408
|
+
*/
|
|
409
|
+
static async executeHookCommand(command, hookType, options = {}) {
|
|
410
|
+
try {
|
|
411
|
+
// Skip if hooks are disabled
|
|
412
|
+
if (HookContextManager.getContext().skipHooks) {
|
|
413
|
+
console.log(`⏭️ Skipping ${hookType} hook (hooks disabled)`);
|
|
414
|
+
return { success: true, skipped: true };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Circuit breaker check
|
|
418
|
+
HookCircuitBreaker.checkExecution(hookType);
|
|
419
|
+
|
|
420
|
+
// Command validation
|
|
421
|
+
const validation = HookCommandValidator.validateCommand(command, hookType);
|
|
422
|
+
|
|
423
|
+
// Show warnings
|
|
424
|
+
for (const warning of validation.warnings) {
|
|
425
|
+
printWarning(warning.message);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Block on errors
|
|
429
|
+
if (!validation.safe) {
|
|
430
|
+
for (const error of validation.errors) {
|
|
431
|
+
printError(error.message);
|
|
432
|
+
}
|
|
433
|
+
return { success: false, blocked: true, errors: validation.errors };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Set hook context for nested calls
|
|
437
|
+
const currentContext = HookContextManager.getContext();
|
|
438
|
+
const newDepth = currentContext.depth + 1;
|
|
439
|
+
HookContextManager.setContext(hookType, newDepth);
|
|
440
|
+
|
|
441
|
+
// Execute the command with safety context
|
|
442
|
+
const result = await this.executeCommand(command, options);
|
|
443
|
+
|
|
444
|
+
return { success: true, result };
|
|
445
|
+
|
|
446
|
+
} catch (err) {
|
|
447
|
+
printError(`Hook execution failed: ${err.message}`);
|
|
448
|
+
return { success: false, error: err.message };
|
|
449
|
+
} finally {
|
|
450
|
+
// Clear context
|
|
451
|
+
HookContextManager.clearContext();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
static async executeCommand(command, options = {}) {
|
|
456
|
+
// This would integrate with the actual command execution system
|
|
457
|
+
// For now, just log what would be executed
|
|
458
|
+
console.log(`🔗 Executing hook command: ${command}`);
|
|
459
|
+
|
|
460
|
+
// Here you would actually execute the command
|
|
461
|
+
// return await execCommand(command, options);
|
|
462
|
+
|
|
463
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Hook Safety CLI Commands
|
|
469
|
+
*/
|
|
470
|
+
export async function hookSafetyCommand(subArgs, flags) {
|
|
471
|
+
const subcommand = subArgs[0];
|
|
472
|
+
|
|
473
|
+
switch (subcommand) {
|
|
474
|
+
case 'validate':
|
|
475
|
+
return await validateConfigCommand(subArgs, flags);
|
|
476
|
+
case 'status':
|
|
477
|
+
return await statusCommand(subArgs, flags);
|
|
478
|
+
case 'reset':
|
|
479
|
+
return await resetCommand(subArgs, flags);
|
|
480
|
+
case 'safe-mode':
|
|
481
|
+
return await safeModeCommand(subArgs, flags);
|
|
482
|
+
default:
|
|
483
|
+
showHookSafetyHelp();
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async function validateConfigCommand(subArgs, flags) {
|
|
488
|
+
const configPath = flags.config || flags.c;
|
|
489
|
+
|
|
490
|
+
console.log('🔍 Validating hook configuration for safety...\n');
|
|
491
|
+
|
|
492
|
+
const result = HookConfigValidator.validateClaudeCodeConfig(configPath);
|
|
493
|
+
|
|
494
|
+
if (result.safe) {
|
|
495
|
+
printSuccess('✅ Hook configuration is safe!');
|
|
496
|
+
if (result.configPath) {
|
|
497
|
+
console.log(`📄 Validated: ${result.configPath}`);
|
|
498
|
+
}
|
|
499
|
+
} else {
|
|
500
|
+
printError('❌ DANGEROUS hook configuration detected!');
|
|
501
|
+
|
|
502
|
+
if (result.errors) {
|
|
503
|
+
console.log('\n🚨 CRITICAL ERRORS:');
|
|
504
|
+
for (const error of result.errors) {
|
|
505
|
+
console.log(`\n${error.message}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (result.warnings) {
|
|
510
|
+
console.log('\n⚠️ WARNINGS:');
|
|
511
|
+
for (const warning of result.warnings) {
|
|
512
|
+
console.log(`\n${warning.message}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
console.log('\n💡 RECOMMENDATIONS:');
|
|
517
|
+
console.log('1. Remove claude commands from Stop hooks');
|
|
518
|
+
console.log('2. Use PostToolUse hooks for specific tools');
|
|
519
|
+
console.log('3. Implement flag-based update patterns');
|
|
520
|
+
console.log('4. Use claude --skip-hooks for manual updates');
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async function statusCommand(subArgs, flags) {
|
|
525
|
+
const context = HookContextManager.getContext();
|
|
526
|
+
const circuitStatus = HookCircuitBreaker.getStatus();
|
|
527
|
+
|
|
528
|
+
console.log('🔗 Hook Safety Status\n');
|
|
529
|
+
|
|
530
|
+
console.log('📊 Current Context:');
|
|
531
|
+
if (context.type) {
|
|
532
|
+
console.log(` 🔄 Hook Type: ${context.type}`);
|
|
533
|
+
console.log(` 📏 Depth: ${context.depth}`);
|
|
534
|
+
console.log(` 🆔 Session: ${context.sessionId}`);
|
|
535
|
+
console.log(` ⏭️ Skip Hooks: ${context.skipHooks ? 'Yes' : 'No'}`);
|
|
536
|
+
console.log(` 🛡️ Safe Mode: ${context.safeMode ? 'Yes' : 'No'}`);
|
|
537
|
+
} else {
|
|
538
|
+
console.log(' ✅ Not currently in hook context');
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
console.log('\n⚡ Circuit Breaker Status:');
|
|
542
|
+
console.log(` 🆔 Session: ${circuitStatus.sessionId}`);
|
|
543
|
+
|
|
544
|
+
if (circuitStatus.executions.length > 0) {
|
|
545
|
+
console.log(' 📊 Hook Executions:');
|
|
546
|
+
for (const exec of circuitStatus.executions) {
|
|
547
|
+
console.log(` • ${exec.hookType}: ${exec.count} times`);
|
|
548
|
+
}
|
|
549
|
+
} else {
|
|
550
|
+
console.log(' ✅ No hook executions in current session');
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async function resetCommand(subArgs, flags) {
|
|
555
|
+
console.log('🔄 Resetting hook safety systems...\n');
|
|
556
|
+
|
|
557
|
+
HookCircuitBreaker.reset();
|
|
558
|
+
HookContextManager.clearContext();
|
|
559
|
+
|
|
560
|
+
printSuccess('✅ Hook safety systems reset successfully!');
|
|
561
|
+
console.log('All execution counters and context cleared.');
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async function safeModeCommand(subArgs, flags) {
|
|
565
|
+
const enable = !flags.disable && !flags.off;
|
|
566
|
+
|
|
567
|
+
if (enable) {
|
|
568
|
+
HookContextManager.setSafeMode(true);
|
|
569
|
+
HookContextManager.setSkipHooks(true);
|
|
570
|
+
printSuccess('🛡️ Safe mode enabled!');
|
|
571
|
+
console.log('• All hooks will be skipped');
|
|
572
|
+
console.log('• Claude commands will show safety warnings');
|
|
573
|
+
console.log('• Additional validation will be performed');
|
|
574
|
+
} else {
|
|
575
|
+
HookContextManager.setSafeMode(false);
|
|
576
|
+
HookContextManager.setSkipHooks(false);
|
|
577
|
+
printSuccess('⚡ Safe mode disabled.');
|
|
578
|
+
console.log('Normal hook execution restored.');
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function showHookSafetyHelp() {
|
|
583
|
+
console.log(`
|
|
584
|
+
🛡️ Hook Safety System - Prevent Infinite Loops & Financial Damage
|
|
585
|
+
|
|
586
|
+
USAGE:
|
|
587
|
+
claude-flow hook-safety <command> [options]
|
|
588
|
+
|
|
589
|
+
COMMANDS:
|
|
590
|
+
validate Validate hook configuration for dangerous patterns
|
|
591
|
+
status Show current hook safety status and context
|
|
592
|
+
reset Reset circuit breakers and execution counters
|
|
593
|
+
safe-mode Enable/disable safe mode (skips all hooks)
|
|
594
|
+
|
|
595
|
+
VALIDATE OPTIONS:
|
|
596
|
+
--config, -c <path> Path to Claude Code settings.json
|
|
597
|
+
|
|
598
|
+
SAFE-MODE OPTIONS:
|
|
599
|
+
--disable, --off Disable safe mode
|
|
600
|
+
|
|
601
|
+
EXAMPLES:
|
|
602
|
+
# Check your Claude Code hooks for dangerous patterns
|
|
603
|
+
claude-flow hook-safety validate
|
|
604
|
+
|
|
605
|
+
# Check specific configuration file
|
|
606
|
+
claude-flow hook-safety validate --config ~/.claude/settings.json
|
|
607
|
+
|
|
608
|
+
# View current safety status
|
|
609
|
+
claude-flow hook-safety status
|
|
610
|
+
|
|
611
|
+
# Reset if circuit breaker is triggered
|
|
612
|
+
claude-flow hook-safety reset
|
|
613
|
+
|
|
614
|
+
# Enable safe mode (skips all hooks)
|
|
615
|
+
claude-flow hook-safety safe-mode
|
|
616
|
+
|
|
617
|
+
# Disable safe mode
|
|
618
|
+
claude-flow hook-safety safe-mode --disable
|
|
619
|
+
|
|
620
|
+
🚨 CRITICAL WARNING:
|
|
621
|
+
Stop hooks that call 'claude' commands create INFINITE LOOPS that can:
|
|
622
|
+
• Bypass API rate limits
|
|
623
|
+
• Cost thousands of dollars per day
|
|
624
|
+
• Make your system unresponsive
|
|
625
|
+
|
|
626
|
+
SAFE ALTERNATIVES:
|
|
627
|
+
• Use PostToolUse hooks instead of Stop hooks
|
|
628
|
+
• Implement flag-based update patterns
|
|
629
|
+
• Use 'claude --skip-hooks' for manual updates
|
|
630
|
+
• Create conditional execution scripts
|
|
631
|
+
|
|
632
|
+
For more information: https://github.com/ruvnet/claude-flow/issues/166
|
|
633
|
+
`);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Emergency CLI flags for Claude commands
|
|
638
|
+
*/
|
|
639
|
+
export function addSafetyFlags(command) {
|
|
640
|
+
// Add safety flags to any claude command
|
|
641
|
+
const context = HookContextManager.getContext();
|
|
642
|
+
|
|
643
|
+
if (context.type) {
|
|
644
|
+
// Automatically add --skip-hooks if in hook context
|
|
645
|
+
if (!command.includes('--skip-hooks')) {
|
|
646
|
+
command += ' --skip-hooks';
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (context.safeMode) {
|
|
651
|
+
// Add additional safety flags in safe mode
|
|
652
|
+
if (!command.includes('--dry-run')) {
|
|
653
|
+
command += ' --dry-run';
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return command;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export default {
|
|
661
|
+
HookContextManager,
|
|
662
|
+
HookCommandValidator,
|
|
663
|
+
HookCircuitBreaker,
|
|
664
|
+
HookConfigValidator,
|
|
665
|
+
SafeHookExecutor,
|
|
666
|
+
hookSafetyCommand,
|
|
667
|
+
addSafetyFlags
|
|
668
|
+
};
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
|
|
46
46
|
// 3. NPX with latest alpha version (prioritized over global)
|
|
47
47
|
async () => {
|
|
48
|
-
return spawn('npx', ['claude-flow@2.0.0-alpha.
|
|
48
|
+
return spawn('npx', ['claude-flow@2.0.0-alpha.27', ...process.argv.slice(2)], { stdio: 'inherit' });
|
|
49
49
|
}
|
|
50
50
|
];
|
|
51
51
|
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe Hook Patterns - Templates for safe Claude Code hook configurations
|
|
3
|
+
*
|
|
4
|
+
* These patterns prevent infinite loops that could cost thousands of dollars
|
|
5
|
+
* by avoiding recursive hook execution when hooks call 'claude' commands.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* DANGEROUS PATTERN - DO NOT USE
|
|
10
|
+
* This creates an infinite loop that can cost thousands of dollars!
|
|
11
|
+
*/
|
|
12
|
+
export const DANGEROUS_PATTERN_EXAMPLE = {
|
|
13
|
+
name: "DANGEROUS: Stop hook calling claude command",
|
|
14
|
+
description: "❌ NEVER USE THIS - Creates infinite recursion loop",
|
|
15
|
+
pattern: {
|
|
16
|
+
"hooks": {
|
|
17
|
+
"Stop": [{
|
|
18
|
+
"matcher": "",
|
|
19
|
+
"hooks": [{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "claude -c -p \"Update all changes to history.md\""
|
|
22
|
+
}]
|
|
23
|
+
}]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
problems: [
|
|
27
|
+
"🚨 Creates infinite loop: Stop → claude command → Stop → claude command...",
|
|
28
|
+
"💰 Can cost $3600+ per day by bypassing rate limits",
|
|
29
|
+
"🚫 Makes system unresponsive",
|
|
30
|
+
"⚡ No built-in protection in Claude Code"
|
|
31
|
+
]
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* SAFE PATTERN 1: Flag-based updates
|
|
36
|
+
* Set a flag instead of calling claude directly
|
|
37
|
+
*/
|
|
38
|
+
export const SAFE_FLAG_PATTERN = {
|
|
39
|
+
name: "Safe Pattern: Flag-based updates",
|
|
40
|
+
description: "✅ Set flag when update needed, run manually",
|
|
41
|
+
pattern: {
|
|
42
|
+
"hooks": {
|
|
43
|
+
"Stop": [{
|
|
44
|
+
"matcher": "",
|
|
45
|
+
"hooks": [{
|
|
46
|
+
"type": "command",
|
|
47
|
+
"command": "bash -c 'echo \"History update needed at $(date)\" > ~/.claude/needs_update && echo \"📝 History update flagged. Run: claude -c -p \\\"Update history.md\\\"\"'"
|
|
48
|
+
}]
|
|
49
|
+
}]
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
benefits: [
|
|
53
|
+
"✅ No recursion - just sets a flag",
|
|
54
|
+
"💰 Zero risk of infinite API calls",
|
|
55
|
+
"🔄 User controls when update runs",
|
|
56
|
+
"📝 Clear instructions for manual execution"
|
|
57
|
+
],
|
|
58
|
+
usage: [
|
|
59
|
+
"1. Hook sets flag when update is needed",
|
|
60
|
+
"2. User sees notification",
|
|
61
|
+
"3. User manually runs: claude -c -p \"Update history.md\"",
|
|
62
|
+
"4. Update runs once safely"
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* SAFE PATTERN 2: PostToolUse hooks instead of Stop hooks
|
|
68
|
+
* React to specific tool usage rather than session end
|
|
69
|
+
*/
|
|
70
|
+
export const SAFE_POST_TOOL_PATTERN = {
|
|
71
|
+
name: "Safe Pattern: PostToolUse hooks",
|
|
72
|
+
description: "✅ React to specific file operations instead of Stop events",
|
|
73
|
+
pattern: {
|
|
74
|
+
"hooks": {
|
|
75
|
+
"PostToolUse": [{
|
|
76
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
77
|
+
"hooks": [{
|
|
78
|
+
"type": "command",
|
|
79
|
+
"command": "echo 'File modified: {file}' >> ~/.claude/modifications.log"
|
|
80
|
+
}]
|
|
81
|
+
}]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
benefits: [
|
|
85
|
+
"✅ Only triggers on actual file changes",
|
|
86
|
+
"🎯 More precise than Stop hooks",
|
|
87
|
+
"📝 Logs specific modifications",
|
|
88
|
+
"🔄 No risk of Stop hook recursion"
|
|
89
|
+
],
|
|
90
|
+
usage: [
|
|
91
|
+
"1. Triggers only when files are written/edited",
|
|
92
|
+
"2. Logs the specific file that was modified",
|
|
93
|
+
"3. Can be used for change tracking",
|
|
94
|
+
"4. Safe to use with any logging command"
|
|
95
|
+
]
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* SAFE PATTERN 3: Conditional execution with skip-hooks
|
|
100
|
+
* Check for hook context before executing claude commands
|
|
101
|
+
*/
|
|
102
|
+
export const SAFE_CONDITIONAL_PATTERN = {
|
|
103
|
+
name: "Safe Pattern: Conditional execution with context check",
|
|
104
|
+
description: "✅ Check if running in hook context before calling claude",
|
|
105
|
+
pattern: {
|
|
106
|
+
"hooks": {
|
|
107
|
+
"Stop": [{
|
|
108
|
+
"matcher": "",
|
|
109
|
+
"hooks": [{
|
|
110
|
+
"type": "command",
|
|
111
|
+
"command": "bash -c 'if [ -z \"$CLAUDE_HOOK_CONTEXT\" ]; then claude -c -p \"Update history.md\" --skip-hooks; else echo \"Skipping update - in hook context\"; fi'"
|
|
112
|
+
}]
|
|
113
|
+
}]
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
benefits: [
|
|
117
|
+
"✅ Checks hook context before execution",
|
|
118
|
+
"🛡️ Uses --skip-hooks flag for safety",
|
|
119
|
+
"🔄 Prevents recursive hook calls",
|
|
120
|
+
"📊 Provides clear feedback"
|
|
121
|
+
],
|
|
122
|
+
usage: [
|
|
123
|
+
"1. Checks CLAUDE_HOOK_CONTEXT environment variable",
|
|
124
|
+
"2. Only runs claude if not in hook context",
|
|
125
|
+
"3. Uses --skip-hooks to prevent triggering more hooks",
|
|
126
|
+
"4. Shows clear message when skipping"
|
|
127
|
+
]
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* SAFE PATTERN 4: Batch processing with scheduled execution
|
|
132
|
+
* Accumulate changes and process them on a schedule
|
|
133
|
+
*/
|
|
134
|
+
export const SAFE_BATCH_PATTERN = {
|
|
135
|
+
name: "Safe Pattern: Batch processing with scheduled execution",
|
|
136
|
+
description: "✅ Accumulate changes and process them separately",
|
|
137
|
+
pattern: {
|
|
138
|
+
"hooks": {
|
|
139
|
+
"Stop": [{
|
|
140
|
+
"matcher": "",
|
|
141
|
+
"hooks": [{
|
|
142
|
+
"type": "command",
|
|
143
|
+
"command": "echo \"$(date): Session ended\" >> ~/.claude/session_log.txt"
|
|
144
|
+
}]
|
|
145
|
+
}]
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
additionalSetup: {
|
|
149
|
+
cronJob: "# Add to crontab (run every hour):\n# 0 * * * * /path/to/update-history.sh",
|
|
150
|
+
updateScript: `#!/bin/bash
|
|
151
|
+
# update-history.sh - Safe batch update script
|
|
152
|
+
LOCK_FILE="~/.claude/update.lock"
|
|
153
|
+
LOG_FILE="~/.claude/session_log.txt"
|
|
154
|
+
|
|
155
|
+
# Check if update is already running
|
|
156
|
+
if [ -f "$LOCK_FILE" ]; then
|
|
157
|
+
echo "Update already in progress"
|
|
158
|
+
exit 1
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# Create lock file
|
|
162
|
+
touch "$LOCK_FILE"
|
|
163
|
+
|
|
164
|
+
# Check if there are new sessions to process
|
|
165
|
+
if [ -f "$LOG_FILE" ] && [ -s "$LOG_FILE" ]; then
|
|
166
|
+
echo "Processing accumulated changes..."
|
|
167
|
+
claude -c -p "Update history.md with recent session data" --skip-hooks
|
|
168
|
+
|
|
169
|
+
# Archive the log
|
|
170
|
+
mv "$LOG_FILE" "~/.claude/session_log_$(date +%Y%m%d_%H%M%S).txt"
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# Remove lock file
|
|
174
|
+
rm "$LOCK_FILE"`
|
|
175
|
+
},
|
|
176
|
+
benefits: [
|
|
177
|
+
"✅ No risk of recursion",
|
|
178
|
+
"⏰ Scheduled processing prevents overload",
|
|
179
|
+
"🔒 Lock file prevents concurrent updates",
|
|
180
|
+
"📦 Batches multiple sessions efficiently"
|
|
181
|
+
]
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* SAFE PATTERN 5: Database/file-based queue system
|
|
186
|
+
* Use external queue for processing commands
|
|
187
|
+
*/
|
|
188
|
+
export const SAFE_QUEUE_PATTERN = {
|
|
189
|
+
name: "Safe Pattern: Queue-based command processing",
|
|
190
|
+
description: "✅ Queue commands for external processing",
|
|
191
|
+
pattern: {
|
|
192
|
+
"hooks": {
|
|
193
|
+
"Stop": [{
|
|
194
|
+
"matcher": "",
|
|
195
|
+
"hooks": [{
|
|
196
|
+
"type": "command",
|
|
197
|
+
"command": "echo '{\"command\": \"update-history\", \"timestamp\": \"'$(date -Iseconds)'\", \"session\": \"'$CLAUDE_SESSION_ID'\"}' >> ~/.claude/command_queue.jsonl"
|
|
198
|
+
}]
|
|
199
|
+
}]
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
processor: `#!/usr/bin/env python3
|
|
203
|
+
# queue-processor.py - Safe command queue processor
|
|
204
|
+
import json
|
|
205
|
+
import subprocess
|
|
206
|
+
import time
|
|
207
|
+
import os
|
|
208
|
+
from pathlib import Path
|
|
209
|
+
|
|
210
|
+
QUEUE_FILE = Path.home() / '.claude' / 'command_queue.jsonl'
|
|
211
|
+
PROCESSING_INTERVAL = 300 # 5 minutes
|
|
212
|
+
|
|
213
|
+
def process_queue():
|
|
214
|
+
if not QUEUE_FILE.exists():
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
# Read and clear queue atomically
|
|
218
|
+
with open(QUEUE_FILE, 'r') as f:
|
|
219
|
+
lines = f.readlines()
|
|
220
|
+
|
|
221
|
+
# Clear the queue
|
|
222
|
+
QUEUE_FILE.unlink()
|
|
223
|
+
|
|
224
|
+
# Process commands
|
|
225
|
+
for line in lines:
|
|
226
|
+
try:
|
|
227
|
+
cmd_data = json.loads(line.strip())
|
|
228
|
+
if cmd_data['command'] == 'update-history':
|
|
229
|
+
print(f"Processing history update for session {cmd_data['session']}")
|
|
230
|
+
subprocess.run([
|
|
231
|
+
'claude', '-c', '-p', 'Update history.md', '--skip-hooks'
|
|
232
|
+
], check=True)
|
|
233
|
+
time.sleep(2) # Rate limiting
|
|
234
|
+
except Exception as e:
|
|
235
|
+
print(f"Error processing command: {e}")
|
|
236
|
+
|
|
237
|
+
if __name__ == '__main__':
|
|
238
|
+
while True:
|
|
239
|
+
try:
|
|
240
|
+
process_queue()
|
|
241
|
+
time.sleep(PROCESSING_INTERVAL)
|
|
242
|
+
except KeyboardInterrupt:
|
|
243
|
+
break`,
|
|
244
|
+
benefits: [
|
|
245
|
+
"✅ Complete separation of hook and claude execution",
|
|
246
|
+
"⏰ Rate limited processing",
|
|
247
|
+
"🔄 Handles multiple queued commands",
|
|
248
|
+
"🛡️ No risk of infinite loops"
|
|
249
|
+
]
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get all safe patterns for documentation generation
|
|
254
|
+
*/
|
|
255
|
+
export const ALL_SAFE_PATTERNS = [
|
|
256
|
+
SAFE_FLAG_PATTERN,
|
|
257
|
+
SAFE_POST_TOOL_PATTERN,
|
|
258
|
+
SAFE_CONDITIONAL_PATTERN,
|
|
259
|
+
SAFE_BATCH_PATTERN,
|
|
260
|
+
SAFE_QUEUE_PATTERN
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Generate safe hooks documentation
|
|
265
|
+
*/
|
|
266
|
+
export function generateSafeHooksGuide() {
|
|
267
|
+
return `# 🛡️ Safe Hook Patterns for Claude Code
|
|
268
|
+
|
|
269
|
+
⚠️ **CRITICAL WARNING**: Stop hooks that call 'claude' commands create infinite loops that can cost thousands of dollars per day!
|
|
270
|
+
|
|
271
|
+
## 🚨 DANGEROUS PATTERN (NEVER USE)
|
|
272
|
+
|
|
273
|
+
${DANGEROUS_PATTERN_EXAMPLE.description}
|
|
274
|
+
|
|
275
|
+
\`\`\`json
|
|
276
|
+
${JSON.stringify(DANGEROUS_PATTERN_EXAMPLE.pattern, null, 2)}
|
|
277
|
+
\`\`\`
|
|
278
|
+
|
|
279
|
+
**Problems:**
|
|
280
|
+
${DANGEROUS_PATTERN_EXAMPLE.problems.map(p => `- ${p}`).join('\n')}
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## ✅ SAFE PATTERNS
|
|
285
|
+
|
|
286
|
+
${ALL_SAFE_PATTERNS.map(pattern => `
|
|
287
|
+
### ${pattern.name}
|
|
288
|
+
|
|
289
|
+
${pattern.description}
|
|
290
|
+
|
|
291
|
+
**Configuration:**
|
|
292
|
+
\`\`\`json
|
|
293
|
+
${JSON.stringify(pattern.pattern, null, 2)}
|
|
294
|
+
\`\`\`
|
|
295
|
+
|
|
296
|
+
**Benefits:**
|
|
297
|
+
${pattern.benefits.map(b => `- ${b}`).join('\n')}
|
|
298
|
+
|
|
299
|
+
${pattern.usage ? `**Usage:**
|
|
300
|
+
${pattern.usage.map((u, i) => `${i + 1}. ${u}`).join('\n')}` : ''}
|
|
301
|
+
|
|
302
|
+
${pattern.additionalSetup ? `**Additional Setup:**
|
|
303
|
+
${pattern.additionalSetup.cronJob ? `
|
|
304
|
+
**Cron Job:**
|
|
305
|
+
\`\`\`bash
|
|
306
|
+
${pattern.additionalSetup.cronJob}
|
|
307
|
+
\`\`\`
|
|
308
|
+
` : ''}
|
|
309
|
+
|
|
310
|
+
${pattern.additionalSetup.updateScript ? `
|
|
311
|
+
**Update Script:**
|
|
312
|
+
\`\`\`bash
|
|
313
|
+
${pattern.additionalSetup.updateScript}
|
|
314
|
+
\`\`\`
|
|
315
|
+
` : ''}` : ''}
|
|
316
|
+
|
|
317
|
+
${pattern.processor ? `
|
|
318
|
+
**Queue Processor:**
|
|
319
|
+
\`\`\`python
|
|
320
|
+
${pattern.processor}
|
|
321
|
+
\`\`\`
|
|
322
|
+
` : ''}
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
`).join('')}
|
|
326
|
+
|
|
327
|
+
## 🚀 Quick Migration Guide
|
|
328
|
+
|
|
329
|
+
### If you currently have this DANGEROUS pattern:
|
|
330
|
+
\`\`\`json
|
|
331
|
+
{
|
|
332
|
+
"hooks": {
|
|
333
|
+
"Stop": [{"hooks": [{"type": "command", "command": "claude -c -p 'Update history'"}]}]
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
\`\`\`
|
|
337
|
+
|
|
338
|
+
### Replace with this SAFE pattern:
|
|
339
|
+
\`\`\`json
|
|
340
|
+
{
|
|
341
|
+
"hooks": {
|
|
342
|
+
"Stop": [{"hooks": [{"type": "command", "command": "touch ~/.claude/needs_update && echo 'Run: claude -c -p \"Update history\"'"}]}]
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
\`\`\`
|
|
346
|
+
|
|
347
|
+
## 🛡️ Hook Safety Tools
|
|
348
|
+
|
|
349
|
+
Use claude-flow's built-in safety tools:
|
|
350
|
+
|
|
351
|
+
\`\`\`bash
|
|
352
|
+
# Check your configuration for dangerous patterns
|
|
353
|
+
claude-flow hook-safety validate
|
|
354
|
+
|
|
355
|
+
# Enable safe mode (skips all hooks)
|
|
356
|
+
claude-flow hook-safety safe-mode
|
|
357
|
+
|
|
358
|
+
# Check current safety status
|
|
359
|
+
claude-flow hook-safety status
|
|
360
|
+
|
|
361
|
+
# Reset circuit breakers if triggered
|
|
362
|
+
claude-flow hook-safety reset
|
|
363
|
+
\`\`\`
|
|
364
|
+
|
|
365
|
+
## 📚 Additional Resources
|
|
366
|
+
|
|
367
|
+
- Issue #166: https://github.com/ruvnet/claude-flow/issues/166
|
|
368
|
+
- Claude Code Hooks Documentation: https://docs.anthropic.com/en/docs/claude-code/hooks
|
|
369
|
+
- Reddit Discussion: https://www.reddit.com/r/ClaudeAI/comments/1ltvi6x/anyone_else_accidentally_create_an_infinite_loop/
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
**Remember**: When in doubt, use flag-based patterns or PostToolUse hooks instead of Stop hooks!
|
|
374
|
+
`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export default {
|
|
378
|
+
DANGEROUS_PATTERN_EXAMPLE,
|
|
379
|
+
ALL_SAFE_PATTERNS,
|
|
380
|
+
generateSafeHooksGuide
|
|
381
|
+
};
|