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.
When & Why to Use This Skill
This Claude skill serves as a comprehensive expert guide for the `claude-hooks-sdk` npm package, designed to help developers build, configure, and troubleshoot custom hooks for Claude Code. It provides deep technical insights into implementing the HookManager for event handling, setting up security guardrails, and utilizing logging utilities for enhanced agent observability. By mastering TypeScript-based transforms and context tracking, users can significantly extend Claude Code's functionality, ensuring safer and more automated AI-assisted development workflows.
Use Cases
- Security Guardrails: Implementing logic to intercept and block dangerous terminal commands (e.g., 'rm -rf') before execution by Claude Code.
- Session Observability: Automatically recording conversation logs and tool usage to external files or databases for auditing and debugging.
- Context Enrichment: Dynamically injecting git metadata, branch information, or project-specific environment variables into Claude's prompt context.
- Automated Notifications: Integrating with Slack or email APIs to notify team members when large changesets or specific file modifications are made.
- File Change Tracking: Monitoring and reporting on all file system modifications during an AI session to maintain a clear audit trail of code edits.
| 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. |
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
bun add claude-hooks-sdk
# or
npm install claude-hooks-sdk
Basic Setup
#!/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
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
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
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
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
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
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
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:
#!/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()}`,
});
Failure Queue
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
const manager = new HookManager({
blockOnFailure: false,
});
Blocking: Hook failures stop Claude Code
const manager = new HookManager({
blockOnFailure: true,
});
Common Patterns
API Integration
manager.onStop(async (input, context) => {
await fetch('https://api.example.com/events', {
method: 'POST',
body: JSON.stringify({ event: input, context }),
});
return success();
});
Command Blocking
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
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
const manager = new HookManager({
handlerTimeout: 30000, // 30 seconds
});