v3/@claude-flow/shared/src/hooks/README.md
Extensible hook points for tool execution, file operations, and lifecycle events. Integrates with the event bus for coordination and monitoring.
import {
createHookRegistry,
createHookExecutor,
HookEvent,
HookPriority
} from '@claude-flow/shared/hooks';
import { createHookRegistry, createHookExecutor } from '@claude-flow/shared/hooks';
import { createEventBus } from '@claude-flow/shared/core';
const registry = createHookRegistry();
const eventBus = createEventBus();
const executor = createHookExecutor(registry, eventBus);
const hookId = registry.register(
HookEvent.PreToolUse,
async (context) => {
console.log(`About to use tool: ${context.tool?.name}`);
// Validate tool parameters
if (!context.tool?.parameters) {
return {
success: false,
error: new Error('Missing tool parameters'),
abort: true // Abort tool execution
};
}
return { success: true };
},
HookPriority.High,
{
name: 'Tool Validation Hook',
timeout: 5000
}
);
const result = await executor.execute(
HookEvent.PreToolUse,
{
event: HookEvent.PreToolUse,
timestamp: new Date(),
tool: {
name: 'Read',
parameters: { path: '/path/to/file.ts' },
category: 'file'
}
}
);
if (result.success) {
console.log(`✓ All hooks passed (${result.hooksExecuted} executed)`);
} else {
console.log(`✗ Hook execution failed: ${result.hooksFailed} failures`);
}
HookEvent.PreToolUse // Before any tool is used
HookEvent.PostToolUse // After tool execution completes
HookEvent.PreRead // Before reading a file
HookEvent.PostRead // After reading a file
HookEvent.PreWrite // Before writing a file
HookEvent.PostWrite // After writing a file
HookEvent.PreEdit // Before editing a file
HookEvent.PostEdit // After editing a file
HookEvent.PreCommand // Before executing bash command
HookEvent.PostCommand // After command execution
HookEvent.SessionStart // When session starts
HookEvent.SessionEnd // When session ends
HookEvent.SessionPause // When session is paused
HookEvent.SessionResume // When session resumes
HookEvent.PreAgentSpawn // Before spawning agent
HookEvent.PostAgentSpawn // After agent spawned
HookEvent.PreAgentTerminate // Before terminating agent
HookEvent.PostAgentTerminate // After agent terminated
HookEvent.PreTaskExecute // Before task execution
HookEvent.PostTaskExecute // After task execution
HookEvent.PreTaskComplete // Before marking task complete
HookEvent.PostTaskComplete // After task completed
HookEvent.PreMemoryStore // Before storing memory
HookEvent.PostMemoryStore // After storing memory
HookEvent.PreMemoryRetrieve // Before retrieving memory
HookEvent.PostMemoryRetrieve // After retrieving memory
HookEvent.OnError // When error occurs
HookEvent.OnWarning // When warning occurs
Control execution order with priority levels:
HookPriority.Critical = 1000 // Execute first
HookPriority.High = 500
HookPriority.Normal = 0 // Default
HookPriority.Low = -500
HookPriority.Lowest = -1000 // Execute last
Hooks can modify context for downstream hooks:
registry.register(
HookEvent.PreCommand,
async (context) => {
// Add risk assessment to context
const isDestructive = context.command?.command.includes('rm -rf');
return {
success: true,
data: {
metadata: {
...context.metadata,
riskLevel: isDestructive ? 'high' : 'low'
}
}
};
},
HookPriority.High
);
// Later hook can access the risk level
registry.register(
HookEvent.PreCommand,
async (context) => {
if (context.metadata?.riskLevel === 'high') {
console.warn('⚠️ High-risk command detected!');
}
return { success: true };
},
HookPriority.Normal
);
Hooks can abort the operation:
registry.register(
HookEvent.PreCommand,
async (context) => {
const isDangerous = context.command?.command.includes('format c:');
if (isDangerous) {
return {
success: false,
abort: true, // Prevent command execution
error: new Error('Dangerous command blocked by security hook')
};
}
return { success: true };
},
HookPriority.Critical
);
Configure timeouts per hook:
registry.register(
HookEvent.PreToolUse,
async (context) => {
// Expensive operation
await analyzeCodeComplexity(context.tool?.parameters);
return { success: true };
},
HookPriority.Normal,
{
timeout: 3000 // 3 second timeout
}
);
Execute hooks for multiple events in parallel:
const results = await executor.executeParallel(
[HookEvent.PreRead, HookEvent.PreWrite, HookEvent.PreEdit],
[
{ event: HookEvent.PreRead, timestamp: new Date(), file: { path: 'a.ts', operation: 'read' } },
{ event: HookEvent.PreWrite, timestamp: new Date(), file: { path: 'b.ts', operation: 'write' } },
{ event: HookEvent.PreEdit, timestamp: new Date(), file: { path: 'c.ts', operation: 'edit' } }
],
{ maxParallel: 3 }
);
Execute hooks sequentially, passing context modifications:
const result = await executor.executeSequential(
[
HookEvent.PreTaskExecute,
HookEvent.PostTaskExecute,
HookEvent.PreTaskComplete,
HookEvent.PostTaskComplete
],
{
event: HookEvent.PreTaskExecute,
timestamp: new Date(),
task: { id: 'task-1', description: 'Process data' }
}
);
// result.finalContext contains all modifications from all hooks
Track hook performance:
const stats = registry.getStats();
console.log(`Total hooks: ${stats.totalHooks}`);
console.log(`Total executions: ${stats.totalExecutions}`);
console.log(`Total failures: ${stats.totalFailures}`);
console.log(`Average execution time: ${stats.avgExecutionTime}ms`);
// Hooks by event type
for (const [event, count] of Object.entries(stats.byEvent)) {
console.log(`${event}: ${count} hooks`);
}
The executor emits coordination events to the event bus:
eventBus.on('hooks:pre-execute', (event) => {
console.log(`Executing ${event.payload.hookCount} hooks for ${event.payload.event}`);
});
eventBus.on('hooks:post-execute', (event) => {
console.log(`Completed in ${event.payload.totalExecutionTime}ms`);
});
eventBus.on('hooks:error', (event) => {
console.error(`Hook ${event.payload.hookId} failed:`, event.payload.error);
});
Critical: Security checks, validation that must happen firstHigh: Important preprocessing (risk assessment, logging)Normal: Standard business logicLow: Optional enhancements, metricsLowest: Cleanup, final loggingregistry.register(
HookEvent.PreToolUse,
async (context) => {
try {
await performValidation(context);
return { success: true };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error : new Error(String(error)),
continueChain: true // Allow other hooks to run
};
}
}
);
Hooks should be fast (<100ms). For expensive operations:
registry.register(
HookEvent.PostToolUse,
async (context) => {
// Queue expensive work for background processing
backgroundQueue.add({
type: 'analyze-tool-usage',
context
});
return { success: true };
},
HookPriority.Low
);
registry.register(
HookEvent.PreCommand,
handler,
HookPriority.Critical,
{
name: 'Security: Prevent Destructive Commands',
metadata: {
purpose: 'Block dangerous shell commands',
blockedPatterns: ['rm -rf /', 'format c:']
}
}
);
import { HookEvent, HookPriority } from '@claude-flow/shared/hooks';
// Register pre-edit hook for context retrieval
registry.register(
HookEvent.PreEdit,
async (context) => {
const filePath = context.file?.path;
if (!filePath) {
return { success: true };
}
// Get similar past edits from ReasoningBank
const similarEdits = await reasoningBank.searchPatterns({
task: `Edit file: ${filePath}`,
k: 5,
minReward: 0.85
});
console.log(`📚 Found ${similarEdits.length} similar past edits`);
return {
success: true,
data: {
metadata: {
...context.metadata,
learningContext: similarEdits,
editCount: similarEdits.length
}
}
};
},
HookPriority.High,
{
name: 'Learning: Pre-Edit Context Retrieval',
timeout: 2000
}
);
// Register post-edit hook for learning storage
registry.register(
HookEvent.PostEdit,
async (context) => {
const success = context.metadata?.success ?? true;
const filePath = context.file?.path;
if (!filePath) {
return { success: true };
}
// Store edit pattern for future learning
await reasoningBank.storePattern({
sessionId: context.session?.id || 'unknown',
task: `Edit file: ${filePath}`,
input: context.file?.previousContent || '',
output: context.file?.content || '',
reward: success ? 0.9 : 0.3,
success,
tokensUsed: estimateTokens(context.file?.content),
latencyMs: context.metadata?.executionTime || 0
});
return { success: true };
},
HookPriority.Normal,
{
name: 'Learning: Post-Edit Pattern Storage',
timeout: 1000
}
);
register(event, handler, priority, options) - Register a hookunregister(hookId) - Unregister a hookunregisterAll(event?) - Unregister all hooks for an eventgetHandlers(event, includeDisabled) - Get handlers for an eventgetHook(hookId) - Get hook by IDenable(hookId) - Enable a hookdisable(hookId) - Disable a hooklistHooks(filter) - List all hooks with optional filtergetEventTypes() - Get all event types with hookscount(event?) - Get hook countgetStats() - Get execution statisticsresetStats() - Reset statisticshas(hookId) - Check if hook existsclear() - Clear all hooksexecute(event, context, options) - Execute hooks for an eventexecuteWithTimeout(event, context, timeout) - Execute with timeoutexecuteParallel(events, contexts, options) - Execute multiple events in parallelexecuteSequential(events, initialContext, options) - Execute sequentially with context chainingsetEventBus(eventBus) - Set event bus for coordinationgetRegistry() - Get hook registryRun the test suite:
cd /workspaces/claude-flow/v3/@claude-flow/shared
npm test -- hooks.test.ts
All 23 tests pass:
v3/@claude-flow/shared/src/hooks/
├── types.ts # Type definitions (~150 lines)
├── registry.ts # Hook registry (~200 lines)
├── executor.ts # Hook executor (~250 lines)
├── index.ts # Main exports
├── hooks.test.ts # Test suite (~20 tests)
└── README.md # This file
hooks:pre-execute, hooks:post-execute, hooks:error)Part of Claude-Flow V3 - See main LICENSE file