// Expert guide for building Claude Code hooks using the claude-hooks-sdk npm package. Use when implementing TypeScript hooks with HookManager, Logger, transforms, context tracking, or troubleshooting SDK features.
| name | claude-hooks-sdk |
| description | Expert guide for building Claude Code hooks using the claude-hooks-sdk npm package. Use when implementing TypeScript hooks with HookManager, Logger, transforms, context tracking, or troubleshooting SDK features. |
You are an expert on the claude-hooks-sdk npm package. Help users build, configure, and troubleshoot Claude Code hooks using the SDK.
bun add claude-hooks-sdk
# or
npm install claude-hooks-sdk
#!/usr/bin/env bun
import { HookManager, success, block, createLogger } from 'claude-hooks-sdk';
const logger = createLogger('my-hook');
const manager = new HookManager({
logEvents: true,
clientId: 'my-hook',
enableContextTracking: true,
trackEdits: true,
});
manager.onPreToolUse(async (input, context) => {
if (input.tool_name === 'Bash' && input.tool_input.command.includes('rm -rf /')) {
return block('Dangerous command blocked');
}
return success();
});
manager.run();
interface HookManagerOptions {
// Logging
logEvents?: boolean;
clientId?: string;
logDir?: string;
debug?: boolean;
// Context & Tracking
enableContextTracking?: boolean;
trackEdits?: boolean;
// Error Handling
blockOnFailure?: boolean;
enableFailureQueue?: boolean;
maxRetries?: number;
handlerTimeout?: number;
}
import { createLogger } from 'claude-hooks-sdk';
const logger = createLogger('my-hook');
logger.info('Always shown');
logger.logDebug('Only shown when DEBUG=1');
logger.warn('Warning message');
logger.error(new Error('Something failed'));
import {
DEFAULT_HANDLER_TIMEOUT_MS, // 30000
DEFAULT_MAX_RETRIES, // 3
EXIT_CODE_SUCCESS, // 0
EXIT_CODE_ERROR, // 1
EXIT_CODE_BLOCK, // 2
} from 'claude-hooks-sdk';
import { ConversationLogger } from 'claude-hooks-sdk';
const conversationLogger = new ConversationLogger();
manager.onUserPromptSubmit((input) => {
conversationLogger.recordUserPrompt(input);
return success();
});
manager.onStop(async (input, context) => {
const turn = await conversationLogger.recordStop(input, context);
console.log(`Turn ${turn.turn_number} recorded`);
return success();
});
import { FileChangeTracker } from 'claude-hooks-sdk';
const fileTracker = new FileChangeTracker();
manager.onPostToolUse((input) => {
fileTracker.recordChange(input);
return success();
});
manager.onStop(async (input) => {
const changes = fileTracker.getBatch(input.session_id);
console.log(`${changes.total_files} files modified`);
return success();
});
manager.onSessionStart(async (input, context) => {
console.log('Session started:', input.session_id);
return success();
});
manager.onUserPromptSubmit(async (input, context) => {
// Add context to Claude's prompt
return success();
});
manager.onPreToolUse(async (input, context) => {
// Block dangerous operations
if (shouldBlock(input)) {
return block('Reason for blocking');
}
return success();
});
manager.onPostToolUse(async (input, context) => {
// React to tool completion
return success();
});
manager.onStop(async (input, context) => {
// Session ending - access context.editedFiles
console.log('Edited files:', context.editedFiles);
return success();
});
interface EventContext {
transactionId: string;
conversationId: string;
promptId?: string;
project_dir?: string;
git?: {
user: string;
email: string;
repo: string;
branch: string;
commit: string;
dirty: boolean;
};
editedFiles?: string[]; // When trackEdits: true
}
One-liner to inject session info into Claude's context:
#!/usr/bin/env bun
import { createUserPromptSubmitHook } from 'claude-hooks-sdk';
createUserPromptSubmitHook();
With customization:
createUserPromptSubmitHook({
format: (name, id) => `Session: ${name} [${id.slice(0, 8)}]`,
customContext: async (input) => `Branch: ${getBranch()}`,
});
const manager = new HookManager({
enableFailureQueue: true,
maxRetries: 3,
onErrorQueueNotEmpty: (size) => {
console.warn(`${size} events queued for retry`);
},
});
Non-blocking (default): Hook failures don't stop Claude Code
const manager = new HookManager({
blockOnFailure: false,
});
Blocking: Hook failures stop Claude Code
const manager = new HookManager({
blockOnFailure: true,
});
manager.onStop(async (input, context) => {
await fetch('https://api.example.com/events', {
method: 'POST',
body: JSON.stringify({ event: input, context }),
});
return success();
});
manager.onPreToolUse(async (input) => {
if (input.tool_name !== 'Bash') return success();
const dangerous = [/rm\s+-rf\s+\//, /dd\s+if=\/dev\/zero/];
for (const pattern of dangerous) {
if (pattern.test(input.tool_input.command)) {
return block('Blocked dangerous command');
}
}
return success();
});
manager.onStop(async (input, context) => {
if (context.editedFiles?.length > 10) {
await sendSlackMessage({
text: `Large changeset: ${context.editedFiles.length} files`,
});
}
return success();
});
logEvents: true is set.claude/hooks/{clientId}/logs/events.jsonlenableContextTracking: truetrackEdits: true requiredconst manager = new HookManager({
handlerTimeout: 30000, // 30 seconds
});