Hook Examples
Real-world hook patterns you can copy and adapt.
Security: Block Dangerous Commands
export async function PreToolUse(input: PreToolUseHookInput) {
if (input.tool_name === 'Bash') {
const cmd = input.tool_input?.command as string;
const blacklist = [
/rm\s+-rf\s+\//,
/dd\s+if=/,
/mkfs/,
/:\(\)\{ :\|:\& \};:/ // fork bomb
];
for (const pattern of blacklist) {
if (pattern.test(cmd)) {
return {
decision: 'deny',
message: `Blocked dangerous command: ${cmd}`
};
}
}
}
return { decision: 'approve' };
}
Audit: Log All Operations
import { appendFileSync } from 'fs';
import { join } from 'path';
export async function PostToolUse(input: PostToolUseHookInput) {
const logPath = join(process.cwd(), 'audit.log');
const entry = {
timestamp: new Date().toISOString(),
tool: input.tool_name,
input: input.tool_input,
success: !input.tool_response?.error,
error: input.tool_response?.error
};
appendFileSync(logPath, JSON.stringify(entry) + '\n');
return {};
}
Integration: Notify External System
export async function SessionStart(input: SessionStartHookInput) {
try {
await fetch('https://hooks.slack.com/services/YOUR/WEBHOOK/URL', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `🤖 Agent session started: ${input.session_id}`
})
});
} catch (error) {
console.error('Failed to notify Slack:', error);
}
return {};
}
Metrics: Track Tool Usage
const toolCounts = new Map<string, number>();
export async function PreToolUse(input: PreToolUseHookInput) {
const count = toolCounts.get(input.tool_name) || 0;
toolCounts.set(input.tool_name, count + 1);
console.log('[Metrics] Tool usage:', Object.fromEntries(toolCounts));
return { decision: 'approve' };
}
export async function SessionEnd(input: SessionEndHookInput) {
console.log('[Final Metrics]', Object.fromEntries(toolCounts));
toolCounts.clear();
return {};
}
Validation: Enforce File Naming
export async function PreToolUse(input: PreToolUseHookInput) {
if (input.tool_name === 'Write' || input.tool_name === 'Edit') {
const filePath = input.tool_input?.file_path as string;
// Enforce kebab-case for file names
const fileName = filePath.split('/').pop();
if (fileName && !/^[a-z0-9-]+\.[a-z]+$/.test(fileName)) {
return {
decision: 'deny',
message: `File name must be kebab-case: ${fileName}`
};
}
}
return { decision: 'approve' };
}
Using AgentrixContext: Task-Aware Audit Logging
Use the factory pattern to access task and workspace information:
import { appendFileSync } from 'fs';
import { join } from 'path';
import type { HookFactory, AgentrixContext } from '@agentrix/shared';
import type { PostToolUseHookInput } from '@anthropic-ai/claude-agent-sdk';
const createHooks: HookFactory = (context: AgentrixContext) => ({
PostToolUse: async (
input: PostToolUseHookInput,
toolUseID: string,
options: { signal: AbortSignal }
) => {
const workspace = context.getWorkspace();
const taskId = context.getTaskId();
const userId = context.getUserId();
// Write audit log to workspace
const logPath = join(workspace, '.agentrix', 'audit.log');
const entry = {
timestamp: new Date().toISOString(),
taskId,
userId,
tool: input.tool_name,
input: input.tool_input,
success: !input.tool_response?.error,
};
appendFileSync(logPath, JSON.stringify(entry) + '\n');
return {};
},
});
export default createHooks;
Using AgentrixContext: Task-Scoped Metrics
Track metrics per task using closure variables:
import type { HookFactory, AgentrixContext } from '@agentrix/shared';
import type {
PreToolUseHookInput,
SessionEndHookInput
} from '@anthropic-ai/claude-agent-sdk';
const createHooks: HookFactory = (context: AgentrixContext) => {
// Closure variable - scoped to this task
const toolCounts = new Map<string, number>();
const startTime = Date.now();
return {
PreToolUse: async (input: PreToolUseHookInput) => {
const count = toolCounts.get(input.tool_name) || 0;
toolCounts.set(input.tool_name, count + 1);
console.log(`[Task ${context.getTaskId()}] Tool: ${input.tool_name} (${count + 1}x)`);
return { decision: 'approve' as const };
},
SessionEnd: async (input: SessionEndHookInput) => {
const duration = Date.now() - startTime;
console.log(`[Task ${context.getTaskId()}] Session Summary:`);
console.log(` Duration: ${Math.round(duration / 1000)}s`);
console.log(` User: ${context.getUserId()}`);
console.log(` Tools used:`, Object.fromEntries(toolCounts));
return {};
},
};
};
export default createHooks;
Using AgentrixContext: Workspace-Aware Security
Validate file operations against workspace boundaries:
import { resolve } from 'path';
import type { HookFactory, AgentrixContext } from '@agentrix/shared';
import type { PreToolUseHookInput } from '@anthropic-ai/claude-agent-sdk';
const createHooks: HookFactory = (context: AgentrixContext) => ({
PreToolUse: async (input: PreToolUseHookInput) => {
const workspace = context.getWorkspace();
// Check file operations stay within workspace
if (input.tool_name === 'Write' || input.tool_name === 'Read' || input.tool_name === 'Edit') {
const filePath = input.tool_input?.file_path as string;
if (filePath) {
const resolvedPath = resolve(workspace, filePath);
// Prevent path traversal
if (!resolvedPath.startsWith(workspace)) {
return {
decision: 'deny' as const,
message: `Access denied: ${filePath} is outside workspace`,
};
}
}
}
return { decision: 'approve' as const };
},
});
export default createHooks;