Agent skill
claude-hooks-sdk
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.
Stars
163
Forks
31
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/claude-hooks-sdk
SKILL.md
Claude Hooks SDK Expert
You are an expert on the claude-hooks-sdk npm package. Help users build, configure, and troubleshoot Claude Code hooks using the SDK.
Quick Reference
Installation
bash
bun add claude-hooks-sdk
# or
npm install claude-hooks-sdk
Basic Setup
typescript
#!/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();
Configuration Options
typescript
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;
}
Logger Utility
typescript
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'));
Constants
typescript
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';
Transforms
ConversationLogger
typescript
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();
});
FileChangeTracker
typescript
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();
});
Hook Event Handlers
typescript
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();
});
Context Tracking
typescript
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
}
Session Context Injection
One-liner to inject session info into Claude's context:
typescript
#!/usr/bin/env bun
import { createUserPromptSubmitHook } from 'claude-hooks-sdk';
createUserPromptSubmitHook();
With customization:
typescript
createUserPromptSubmitHook({
format: (name, id) => `Session: ${name} [${id.slice(0, 8)}]`,
customContext: async (input) => `Branch: ${getBranch()}`,
});
Failure Queue
typescript
const manager = new HookManager({
enableFailureQueue: true,
maxRetries: 3,
onErrorQueueNotEmpty: (size) => {
console.warn(`${size} events queued for retry`);
},
});
Blocking vs Non-Blocking
Non-blocking (default): Hook failures don't stop Claude Code
typescript
const manager = new HookManager({
blockOnFailure: false,
});
Blocking: Hook failures stop Claude Code
typescript
const manager = new HookManager({
blockOnFailure: true,
});
Common Patterns
API Integration
typescript
manager.onStop(async (input, context) => {
await fetch('https://api.example.com/events', {
method: 'POST',
body: JSON.stringify({ event: input, context }),
});
return success();
});
Command Blocking
typescript
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();
});
File Change Notifications
typescript
manager.onStop(async (input, context) => {
if (context.editedFiles?.length > 10) {
await sendSlackMessage({
text: `Large changeset: ${context.editedFiles.length} files`,
});
}
return success();
});
Troubleshooting
Logs not appearing
- Check
logEvents: trueis set - Log path:
.claude/hooks/{clientId}/logs/events.jsonl
Context not enriched
- Verify
enableContextTracking: true - For git metadata: must be in a git repo
- For editedFiles:
trackEdits: truerequired
Handler timeout
typescript
const manager = new HookManager({
handlerTimeout: 30000, // 30 seconds
});
Resources
Didn't find tool you were looking for?