Claude Code Plugins

Community-maintained marketplace

Feedback

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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: true is 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: true required

Handler timeout

const manager = new HookManager({
  handlerTimeout: 30000,  // 30 seconds
});

Resources