npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

claude-hooks-sdk

v0.8.6

Published

Type-safe TypeScript SDK for building Claude Code hook extensions with edit tracking, context correlation, transform utilities, and non-blocking error handling

Readme

claude-hooks-sdk

Type-safe TypeScript SDK for building Claude Code hook extensions

npm version TypeScript License: MIT

Features

Core Features

  • Session Context Injection - ONE-LINE helper to inject session info into Claude's context (saves 50-100+ tokens/request)
  • Full Type Safety - Complete TypeScript types for all 10 Claude Code hook events
  • Fluent API - Intuitive manager.onPreToolUse(...) handler registration
  • Non-Blocking by Default - Hook failures don't block Claude Code (opt-in blocking available)
  • Built-in Event Logging - Enable with one flag, organized by client ID
  • Edit Tracking - Automatically track files modified during Claude's response
  • Context Tracking - Automatic transaction IDs, prompt IDs, git metadata, and parent session tracking
  • Failure Queue - Sequential event processing with automatic retry (FIFO)
  • Transcript Access - Built-in utilities for parsing and searching conversation history
  • Plugin System - Extensible architecture for custom integrations
  • Async/Await Support - Full async handler support for API calls, database queries, etc.

Transform Utilities (v0.6.0)

  • 🔄 Conversation Logging - Track user prompts and assistant responses between Stop events
  • 📄 File Change Tracking - Monitor Write/Edit/MultiEdit operations
  • Todo Progress Tracking - Extract and monitor TodoWrite events with completion percentages
  • 🤖 AI Summarization - Auto-summarize Stop events using Claude Haiku API

Advanced Features (v0.7.0)

  • 💾 Persistent State - Durable SQLite/file/memory storage for state that survives restarts
  • 📊 Session Analytics - Automatic cost tracking, performance metrics, token usage analysis
  • 🎬 Event Recording & Replay - Record sessions to JSONL and replay for testing
  • 📡 Real-time Streaming - SSE-based event streaming to dashboards and monitoring tools
  • 🔌 Middleware System - Composable middleware with rate limiting, deduplication, PII redaction
  • 🚨 Anomaly Detection - Detect unusual patterns (error spikes, token anomalies, response time issues)

Table of Contents

Installation

Method 1: npm Package (For Developers)

Install the SDK to build custom hooks:

npm install claude-hooks-sdk
# or
bun add claude-hooks-sdk

Method 2: Claude Code Plugin (For Ready-to-Use Examples + Expert Skill)

Install pre-built example hooks and comprehensive SDK skill via the Claude Code marketplace:

# Add the marketplace
/plugin marketplace add hgeldenhuys/claude-hooks-sdk

# Install the plugin (includes examples + skill)
/plugin install claude-hooks-sdk-examples

What you get:

📚 Expert Skill - Makes Claude Code self-aware of the SDK:

/skill claude-hooks-sdk
# Now Claude can answer: "How do I enable edit tracking?"
# "Show me a blocking hook example", etc.

🔌 Production-Ready Hooks:

  • event-logger - Full-featured event logging with edit tracking
  • failure-queue-demo - Automatic retry queue demonstration
  • edit-tracking - Track files modified by Claude
  • non-blocking - Non-blocking error handling example

⭐ Quick Start: Session Context Injection (Recommended)

The #1 most valuable hook pattern - automatically inject session info into Claude's context on every prompt, saving 50-100+ tokens per request.

Create .claude/hooks/UserPromptSubmit.ts:

#!/usr/bin/env bun
import { createUserPromptSubmitHook } from 'claude-hooks-sdk';

// That's it! Session context automatically available to Claude
createUserPromptSubmitHook();

Register in .claude/settings.json:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "bun \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/UserPromptSubmit.ts"
          }
        ]
      }
    ]
  }
}

What this does:

  • ✅ Injects session ID and name into Claude's context automatically
  • ✅ Saves 50-100+ tokens per request (no tool calls needed)
  • ✅ Zero latency - session info instantly available
  • ✅ Enables session-aware workflows, logging, and file management

Why this matters: Claude doesn't know its own session ID by default. Without this hook, it has to use tools to read .claude/sessions.json on every request where it needs session info. This hook makes it available instantly.

createUserPromptSubmitHook({
  // Custom session format
  format: (name, id) => `🔵 Session: ${name} [${id.slice(0, 8)}]`,

  // Add additional context
  customContext: async (input) => {
    const branch = execSync('git branch --show-current').toString().trim();
    return `Git branch: ${branch}`;
  },

  // Custom error handling
  onError: (err) => console.error('[Hook Error]', err)
});

Quick Start: General Hook Development

Create a hook file (e.g., .claude/hooks/my-hook.ts):

#!/usr/bin/env bun
import { HookManager, success, block } from 'claude-hooks-sdk';

const manager = new HookManager({
  logEvents: true,          // Enable automatic event logging
  clientId: 'my-hook',      // Organize logs by client
});

// Block dangerous bash commands
manager.onPreToolUse(async (input) => {
  if (input.tool_name === 'Bash' && input.tool_input.command.includes('rm -rf /')) {
    return block('Dangerous command detected!');
  }
  return success();
});

// Log session starts (with auto-generated session name)
manager.onSessionStart(async (input) => {
  console.log(`Session started: ${input.session_name} (${input.session_id})`);
  // Example output: "Session started: brave-elephant (af13b3cd-5185-...)"
  return success();
});

manager.run();

That's it! Events are automatically logged to .claude/hooks/my-hook/logs/events.jsonl

Register the hook in .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.ts"
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.ts"
          }
        ]
      }
    ]
  }
}

Transform Utilities

NEW: Convenient transform utilities for common hook patterns.

The SDK now includes powerful transform utilities that make it easy to implement common logging and analytics patterns. These are production-ready and can be used in backend services.

Available Transforms

  1. ConversationLogger - Track conversation turns with user prompts and assistant responses
  2. FileChangeTracker - Monitor file modifications from Write/Edit/MultiEdit tools
  3. TodoTracker - Track todo items and progress
  4. AISummarizer - Auto-summarize Stop events using Claude Haiku

Quick Start with Transforms

import {
  HookManager,
  success,
  ConversationLogger,
  FileChangeTracker,
  TodoTracker,
} from 'claude-hooks-sdk';

const conversationLogger = new ConversationLogger();
const fileTracker = new FileChangeTracker();
const todoTracker = new TodoTracker();

const manager = new HookManager();

manager
  .onUserPromptSubmit((input) => {
    conversationLogger.recordUserPrompt(input);
    return success();
  })
  .onPreToolUse((input) => {
    conversationLogger.recordToolUse(input.tool_name);
    return success();
  })
  .onPostToolUse((input) => {
    fileTracker.recordChange(input);
    todoTracker.recordTodoWrite(input);
    return success();
  })
  .onStop(async (input, context) => {
    // Get all transform data
    const turn = await conversationLogger.recordStop(input, context);
    const files = fileTracker.getBatch(input.session_id);
    const todos = todoTracker.getSnapshot(input.session_id);

    console.log('Conversation Turn:', turn.turn_number);
    console.log('Files Modified:', files.total_files);
    console.log('Todo Progress:', todoTracker.getCompletionPercentage(input.session_id) + '%');

    return success();
  });

manager.run();

Example Output

Conversation Turn:

{
  "assistant": {
    "content": "I'll help you implement that feature...",
    "timestamp": "2025-11-21T23:00:00.000Z",
    "toolsUsed": ["Read", "Edit", "TodoWrite"]
  },
  "user_prompts": [
    { "text": "Can you add error handling?", "timestamp": "..." }
  ],
  "turn_number": 5,
  "session_id": "abc123"
}

File Changes:

{
  "file": "src/utils/api.ts",
  "operation": "modified",
  "tool": "Edit",
  "timestamp": "2025-11-21T23:00:00.000Z",
  "session_id": "abc123",
  "size_hint": 1234
}

Todo Progress:

{
  "event_type": "todos_updated",
  "todos": [...],
  "completed": 3,
  "in_progress": 1,
  "pending": 2,
  "timestamp": "2025-11-21T23:00:00.000Z"
}

AI Summary (requires ANTHROPIC_API_KEY):

{
  "summary": "Added error handling to API utility functions",
  "model": "claude-haiku-3-5-20241022",
  "input_tokens": 125,
  "output_tokens": 12,
  "timestamp": "2025-11-21T23:00:00.000Z"
}

Complete Examples

See examples/transforms/ for complete working examples:

  • conversation-logger.ts - Chat-style logging
  • file-changes-logger.ts - File modification tracking
  • todo-logger.ts - Todo progress monitoring
  • ai-summarizer.ts - Automatic Stop event summaries
  • all-transforms.ts - All transforms combined

Transform API Reference

ConversationLogger

const logger = new ConversationLogger();

// Record events
logger.recordUserPrompt(input);
logger.recordToolUse(toolName);
const turn = await logger.recordStop(input, context);

// Utilities
logger.getTurnNumber(); // Current turn number
logger.reset(); // Reset state

FileChangeTracker

const tracker = new FileChangeTracker();

// Record changes
const change = tracker.recordChange(input);

// Get data
const batch = tracker.getBatch(sessionId);
const files = tracker.getUniqueFiles(sessionId);
const count = tracker.getFileModificationCount(sessionId, filePath);

// Cleanup
tracker.clearSession(sessionId);

TodoTracker

const tracker = new TodoTracker();

// Record todos
const event = tracker.recordTodoWrite(input);

// Get data
const snapshot = tracker.getSnapshot(sessionId);
const inProgress = tracker.getTodosByStatus(sessionId, 'in_progress');
const pct = tracker.getCompletionPercentage(sessionId);

// Cleanup
tracker.clearSession(sessionId);

AISummarizer

const summarizer = new AISummarizer({
  apiKey: process.env.ANTHROPIC_API_KEY!,
  model: 'claude-haiku-3-5-20241022',
});

// Generate summaries
const summary = await summarizer.summarizeStop(input, context);

// Custom prompts
const custom = await summarizer.summarizeWithPrompt(
  content,
  'Summarize: {content}',
  sessionId
);

// Utilities
summarizer.getTurnNumber(); // Current turn
summarizer.reset(); // Reset counter

Production Usage

manager.onStop(async (input, context) => {
  const turn = await conversationLogger.recordStop(input, context);
  const files = fileTracker.getBatch(input.session_id);
  const todos = todoTracker.getSnapshot(input.session_id);

  // Send to your analytics backend
  await fetch('https://api.example.com/sessions/events', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      type: 'stop',
      conversation: turn,
      files,
      todos,
      timestamp: new Date().toISOString(),
    }),
  });

  return success();
});

Built-in Event Logging

The SDK includes automatic event logging - just enable it with a flag!

const manager = new HookManager({
  logEvents: true,              // Enable logging
  clientId: 'my-extension',     // Organize by client
  // logDir: '/custom/path',    // Optional: custom directory
});

Logs are saved to: $CLAUDE_PROJECT_DIR/.claude/hooks/{clientId}/logs/events.jsonl (Falls back to current directory if CLAUDE_PROJECT_DIR not set)

Each log entry contains:

{
  "input": {
    "hook": {                    // Full hook event
      "hook_event_name": "PreToolUse",
      "session_id": "...",
      "tool_name": "Read",
      "tool_input": {...}
    },
    "conversation": {            // Last transcript line
      "type": "user",
      "message": {...}
    },
    "context": {                 // Event correlation context
      "transactionId": "tx_...",
      "conversationId": "...",
      "project_dir": "/path/to/project",
      "promptId": "prompt_...",
      "git": {...}
    },
    "timestamp": "2025-11-21T..."
  },
  "output": {                    // What your handler returned
    "exitCode": 0,
    "success": true,
    "hasOutput": false,
    "hasStdout": false,
    "hasStderr": false
  }
}

View logs:

# From project root (uses CLAUDE_PROJECT_DIR)
tail -f .claude/hooks/my-extension/logs/events.jsonl | jq .

# All events
cat .claude/hooks/my-extension/logs/events.jsonl | jq .

# Filter by event type
cat .claude/hooks/my-extension/logs/events.jsonl | jq 'select(.input.hook.hook_event_name == "PreToolUse")'

# View just context
tail -f .claude/hooks/my-extension/logs/events.jsonl | jq '.input.context'

# View event summary
cat .claude/hooks/my-extension/logs/events.jsonl | jq -c '{event: .input.hook.hook_event_name, tool: .input.hook.tool_name, tx: .input.context.transactionId}'

Edit Tracking

Track all files modified during Claude's response!

Enable with one flag to automatically collect all files edited via the Edit tool:

const manager = new HookManager({
  trackEdits: true,  // ← Enable edit tracking
  logEvents: true
});

manager.onStop(async (input) => {
  const editedFiles = (input as any).context?.editedFiles;

  if (editedFiles) {
    console.log(`Files edited: ${editedFiles.length}`);
    editedFiles.forEach(file => console.log(`  - ${file}`));

    // Auto-commit changed files
    execSync(`git add ${editedFiles.join(' ')}`);
    execSync(`git commit -m "Claude edits"`);
  }

  return success();
});

Result in Stop event:

{
  "context": {
    "transactionId": "tx_...",
    "editedFiles": [
      "/project/src/app.ts",
      "/project/src/utils.ts"
    ]
  }
}

Use cases:

  • Auto-commit changed files
  • Run tests on modified files only
  • Slack notifications for large changesets
  • Code review workflows
  • Lint only changed files

See docs/guides/EDIT-TRACKING.md for complete documentation.


Context Tracking (Event Correlation)

Enabled by default - All events are automatically enriched with correlation context!

What's Tracked

Every hook event gets enriched with:

{
  hook_event_name: "PreToolUse",
  // ... normal event fields ...
  context: {
    transactionId: "tx_1234567890_abc123def",  // Persists across session
    conversationId: "session-uuid",             // Session identifier
    promptId: "prompt_1234567890",              // Last prompt ID
    project_dir: "/path/to/project",            // CLAUDE_PROJECT_DIR environment variable
    git: {                                      // Git repository metadata
      user: "John Doe",
      email: "[email protected]",
      repo: "https://github.com/user/repo.git",
      branch: "main",
      commit: "abc1234...",
      dirty: false                              // Has uncommitted changes?
    }
  }
}

Transaction ID Lifecycle

SessionStart  → Generate new transaction ID
                ↓
All Events    → Same transaction ID (correlation!)
                ↓
SessionEnd    → Clear context

Usage Example

const manager = new HookManager({
  enableContextTracking: true,  // Default: true
  clientId: 'my-hook',
});

manager.onPreToolUse(async (input: any) => {
  console.log(`Transaction: ${input.context.transactionId}`);
  console.log(`Prompt: ${input.context.promptId}`);
  console.log(`Git Branch: ${input.context.git?.branch}`);

  // Send to analytics with correlation
  await analytics.track({
    event: 'tool_use',
    transactionId: input.context.transactionId,
    promptId: input.context.promptId,
    tool: input.tool_name,
  });

  return success();
});

Benefits

  • Event Correlation: Link all events in a session with same transaction ID
  • Prompt Tracking: Know which prompt triggered which tool uses
  • Git Context: Track which branch/commit events occurred on
  • Parent-Child Tracking: Subagent events include parentSessionId and agentId

Session Naming

Automatically assign user-friendly names to Claude Code sessions!

The SDK now generates memorable names for all sessions (e.g., "brave-elephant", "clever-dolphin") instead of UUIDs. Session names are:

  • Automatic - Generated on SessionStart, no configuration needed
  • Persistent - Saved to .claude/sessions.json for resume support
  • Bidirectional - Lookup session ID by name or name by ID
  • Manual - Rename sessions via config or CLI scripts

Usage

import { HookManager, getSessionName, getSessionId } from 'claude-hooks-sdk';

const manager = new HookManager();

manager.onSessionStart(async (input) => {
  // session_name is auto-populated by the SDK
  console.log(`Session: ${input.session_name}`);
  // Output: "Session: brave-elephant"

  return { exitCode: 0 };
});

When Claude Code starts, you'll see:

📝 Session: brave-elephant

Ready. How can I help?

Resume by Name

# Find session ID by name
bun .claude/scripts/session-lookup.ts brave-elephant
# Output: af13b3cd-5185-42df-aa85-3bf6e52e1810

# Resume using that ID
claude --resume $(bun .claude/scripts/session-lookup.ts brave-elephant)

Rename Sessions

# Rename by current name
bun .claude/scripts/session-rename.ts brave-elephant "refactor-sse"

# Rename by session ID
bun .claude/scripts/session-rename.ts af13b3cd "bugfix-viewer"

Manual Names via Config

Set custom names in .claude-plugin/config.json:

{
  "session-namer": {
    "manualNames": {
      "af13b3cd-5185-42df-aa85-3bf6e52e1810": "refactor-realtime-sse"
    }
  }
}

Conversation Logger Integration

The conversation-logger plugin shows session names in the viewer:

╔══════════════════════════════════════════════════════╗
║ 🤖 Agent Started                                     ║
║   Session: brave-elephant (af13b3cd)                 ║
║   Source: resume                                     ║
╚══════════════════════════════════════════════════════╝

API

import {
  getSessionName,
  getSessionId,
  renameSession,
  listSessions,
} from 'claude-hooks-sdk';

// Lookup name by ID
const name = getSessionName('af13b3cd-5185-42df-aa85-3bf6e52e1810');
// Returns: "brave-elephant"

// Lookup ID by name
const id = getSessionId('brave-elephant');
// Returns: "af13b3cd-5185-42df-aa85-3bf6e52e1810"

// Rename a session
renameSession('af13b3cd-5185-42df-aa85-3bf6e52e1810', 'new-name');

// List all sessions
const sessions = listSessions();
// Returns: [{ sessionId: '...', info: { name: '...', created: '...', ... } }]

Name format: adjective-animal (e.g., "brave-elephant", "clever-dolphin")

  • Uses unique-names-generator library
  • Collision handling with -2, -3 suffixes
  • Stored in .claude/sessions.json

Non-Blocking Error Handling

By default, hook failures DON'T block Claude Code!

This is critical for reliability - a failed API call or network timeout shouldn't freeze your session.

const manager = new HookManager({
  blockOnFailure: false,  // Default: non-blocking
  enableFailureQueue: true,
  maxRetries: 3
});

manager.onPreToolUse(async (input) => {
  // If this fails, Claude Code continues normally
  await fetch('https://analytics.com/track', {
    method: 'POST',
    body: JSON.stringify(input),
    signal: AbortSignal.timeout(5000)
  });

  return success();
});

What happens when handler fails:

| Mode | Exit Code | Claude Code | Queue | User Impact | |------|-----------|-------------|-------|-------------| | Non-Blocking (default) | 0 (success) | Continues | Event queued for retry | None ✅ | | Blocking (opt-in) | 1 (error) | Blocked | No queue | Error shown ❌ |

Use non-blocking for:

  • Analytics/telemetry
  • Logging services
  • Notifications (Slack, Discord)
  • Metrics (Datadog, Prometheus)
  • Non-critical API calls

Use blocking for:

  • Security enforcement
  • Access control
  • Critical validation
  • Compliance checks

See docs/guides/NON-BLOCKING-HOOKS.md for complete documentation.

  • Analytics Ready: Perfect for sending to analytics platforms
  • Debugging: Easily trace event chains

Disabling Context Tracking

const manager = new HookManager({
  enableContextTracking: false,  // Disable if not needed
});

Failure Queue (Sequential Event Processing)

The SDK includes a built-in failure queue for resilient, sequential event processing. When enabled, failed events are automatically queued and retried, ensuring FIFO (First-In-First-Out) ordering.

Why Use the Failure Queue?

  • Guaranteed Ordering: Events are processed sequentially - if one fails, subsequent events wait
  • Automatic Retry: Failed events retry automatically with configurable max attempts
  • No Lost Events: Failures are persisted to disk and survive restarts
  • Consumer Notifications: Get notified when the queue has items waiting

Basic Usage

const manager = new HookManager({
  enableFailureQueue: true,        // Enable the queue
  clientId: 'my-extension',        // Required for queue organization
  maxRetries: 3,                   // Max retry attempts (default: 3)

  // Optional: get notified when queue has items
  onErrorQueueNotEmpty: async (queueSize, failedEvents) => {
    console.log(`⚠️ ${queueSize} events in error queue`);
    // Could trigger a notification, log to monitoring, etc.
  }
});

manager.onPreToolUse(async (input) => {
  // If this handler fails (returns exitCode !== 0 or throws),
  // the event is automatically added to the error queue
  const result = await processEvent(input);

  if (!result.success) {
    return error('Processing failed, will retry');
  }

  return success();
});

manager.run();

How It Works

Every hook event automatically drains the queue first (FIFO order):

  1. Hook Event Arrives → Check if queue has events
  2. Queue Not Empty? → Drain queue synchronously:
    • Call onErrorQueueNotEmpty callback
    • Retry all queued events in FIFO order
    • Successfully processed events removed from queue
    • Still-failing events remain (retry count incremented)
  3. Queue Still Not Empty After Drain? → Queue the new event, return success
  4. Queue Empty? → Process the new event
  5. New Event Fails? → Add to queue for next hook event

Error Queue File Format

Failed events are saved to $CLAUDE_PROJECT_DIR/.claude/hooks/{clientId}/error-queue.jsonl:

{"event": {...}, "error": "Connection timeout", "timestamp": "2025-11-21T...", "retryCount": 0}
{"event": {...}, "error": "API rate limit", "timestamp": "2025-11-21T...", "retryCount": 1}

Automatic Queue Draining

The queue is automatically drained on every hook event - no cron jobs needed!

When a hook event arrives:

  1. SDK checks if queue has items
  2. If yes, drain queue synchronously (retry all events in FIFO order)
  3. Successfully processed events removed
  4. Still-failing events remain (retry count incremented)
  5. If queue still has items after draining, queue the new event
  6. If queue is empty, process the new event

Queue Behavior

  • FIFO Order: Events always processed in order they failed
  • Automatic Retry: Every hook event triggers drain attempt
  • Retry Limit: After maxRetries, events are dropped
  • No External Jobs: No cron jobs or background workers needed

Optional: Manual Queue Inspection

For monitoring or debugging:

// Check queue status (optional)
const status = manager.getQueueStatus();
console.log(`Queue has ${status.size} events`);

// Manually drain if needed (optional - normally automatic)
const result = await manager.drainQueue();
console.log(`Processed: ${result.processed}`);

Use Cases

  • API Integrations: Retry failed API calls without losing events
  • Database Operations: Ensure all events are persisted even during outages
  • External Services: Queue events when external service is down
  • Rate Limiting: Respect rate limits by queuing excess events

📖 For detailed documentation, see docs/guides/FAILURE-QUEUE.md


API Reference

HookManager

The main class for registering and executing hook handlers.

const manager = new HookManager(options?: HookManagerOptions);

Options:

interface HookManagerOptions {
  debug?: boolean;          // Enable debug output
  logEvents?: boolean;      // Enable event logging
  clientId?: string;        // Client ID for organizing logs
  logDir?: string;          // Custom log directory

  // Failure Queue Options
  enableFailureQueue?: boolean;  // Enable sequential event processing with retry
  maxRetries?: number;           // Max retry attempts (default: 3)
  onErrorQueueNotEmpty?: (       // Callback when queue has items
    queueSize: number,
    failedEvents: FailedEvent[]
  ) => Promise<void> | void;
}

Methods

Event Handlers:

  • onPreToolUse(handler) - Before tool execution
  • onPostToolUse(handler) - After tool execution
  • onNotification(handler) - On Claude notifications
  • onUserPromptSubmit(handler) - Before processing user input
  • onStop(handler) - When main agent finishes
  • onSubagentStop(handler) - When subagent completes
  • onPreCompact(handler) - Before context compaction
  • onSessionStart(handler) - Session lifecycle start
  • onSessionEnd(handler) - Session lifecycle end

Execution:

  • run() - Execute handlers (reads stdin, writes stdout/stderr)
  • execute(input) - Manually execute handlers (advanced usage)

Plugins:

  • use(plugin) - Register a plugin to extend functionality

Failure Queue (Optional - queue drains automatically):

  • drainQueue() - Manually drain queue (optional), returns { processed, remaining, dropped }
  • getQueueStatus() - Inspect queue status, returns { size, events }

Handler Response Helpers

import { success, block, error } from 'claude-hooks-sdk';

// Allow execution to continue
return success();

// Block execution (PreToolUse only)
return block('Reason for blocking');

// Return error (non-blocking)
return error('Error message');

// Custom response
return {
  exitCode: 0,  // 0 = success, 2 = blocking error
  stdout: 'Message to Claude',
  output: {
    continue: true,
    // ... hook-specific fields
  }
};

Hook Context

Each handler receives a context object with transcript utilities:

manager.onSessionStart(async (input, context) => {
  // Get a specific transcript line
  const line = await context.getTranscriptLine(42);

  // Get full transcript
  const transcript = await context.getFullTranscript();

  // Search transcript
  const matches = await context.searchTranscript(line =>
    line.content?.role === 'user'
  );

  return success();
});

Plugin System

Create custom plugins to extend functionality:

import { HookPlugin } from 'claude-hooks-sdk';

const myPlugin: HookPlugin = {
  name: 'my-plugin',

  async onBeforeExecute(input, context) {
    // Called before handlers execute
    console.log(`Event: ${input.hook_event_name}`);
  },

  async onAfterExecute(input, result, context) {
    // Called after handlers execute
    if (result.exitCode !== 0) {
      console.error('Handler failed');
    }
  }
};

manager.use(myPlugin);

Documentation

🧠 Interactive Skill (Recommended)

Install the claude-hooks-sdk skill for interactive guidance:

/plugin marketplace add hgeldenhuys/claude-hooks-sdk
/plugin install claude-hooks-sdk-examples
/skill claude-hooks-sdk

The skill makes Claude Code self-aware of the SDK - ask questions like:

  • "How do I enable edit tracking?"
  • "Show me a blocking hook example"
  • "Why aren't my logs appearing?"

📚 Written Guides

Comprehensive guides and references in the docs/ directory:

Feature Guides

📖 Reference

🚀 Releases

See the Documentation Index for complete list.

Examples

Basic Examples

Complete working examples in sample-extension/:

Advanced Examples

Looking for production-ready, sophisticated examples? Check out claude-hooks-sdk-examples for:

  • custom-backend - Real-time event visualization server with markdown rendering, thinking blocks, smart polling, and analytics dashboards

Security Validation

manager.onPreToolUse(async (input) => {
  if (input.tool_name !== 'Bash') {
    return success();
  }

  const command = input.tool_input.command;

  // Block dangerous patterns
  if (/rm\s+-rf\s+\//.test(command)) {
    return block('Dangerous command: rm -rf /');
  }

  // Warn about sudo
  if (command.includes('sudo')) {
    return {
      exitCode: 0,
      output: {
        hookSpecificOutput: {
          hookEventName: 'PreToolUse',
          permissionDecision: 'ask',
          permissionDecisionReason: 'Requires elevated privileges'
        }
      }
    };
  }

  return success();
});

API Integration Plugin

const apiPlugin: HookPlugin = {
  name: 'api-logger',
  async onAfterExecute(input, result) {
    await fetch('https://my-api.com/hooks', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        event: input.hook_event_name,
        sessionId: input.session_id,
        timestamp: new Date().toISOString(),
        success: result.exitCode === 0
      })
    });
  }
};

manager.use(apiPlugin);

Context Injection

manager.onUserPromptSubmit(async (input) => {
  return {
    exitCode: 0,
    stdout: `
Current time: ${new Date().toISOString()}
Working directory: ${input.cwd}
Session: ${input.session_id}
    `.trim(),
    output: { continue: true }
  };
});

Transcript Analysis

manager.onStop(async (input, context) => {
  const transcript = await context.getFullTranscript();

  // Count tool uses
  const toolUses = transcript.filter(line =>
    line.content?.type === 'tool_use'
  );

  console.log(`Session used ${toolUses.length} tools`);

  return success();
});

Fast Transcript Lookups with Line Numbers

Conversation turns automatically include line numbers for blazing-fast retroactive transcript lookups:

const logger = new ConversationLogger();

manager.onStop(async (input, context) => {
  const turn = await logger.recordStop(input, context);

  // Line number automatically included!
  const lineNumber = turn.assistant.transcript_line_number;  // e.g., 42

  // Store for fast retrieval later
  await db.saveTransactionPointer({
    session_id: input.session_id,
    line_number: lineNumber,  // Fast lookup (0.002s with sed)
    uuid: lastLine.uuid,      // Validation
  });

  return success();
});

// Later: Retrieve specific transcript line in 0.002s
async function getTranscriptEntry(sessionId: string, lineNumber: number) {
  const path = `~/.claude/projects/.../${sessionId}.jsonl`;
  const line = await Bun.$`sed -n '${lineNumber}p' ${path}`.text();
  return JSON.parse(line);
}

Performance: Line number lookups are 1000x faster than UUID searches (0.002s vs 2.5s).

ConversationLine automatically includes:

  • lineNumber - 1-indexed line number in transcript JSONL
  • uuid - Unique identifier for validation
  • All original transcript fields

Custom Backend Integration

Complete production-ready example with mini Bun server and real-time HTML dashboard:

👉 examples/custom-backend/ - Full backend integration example

Features:

  • 🔌 HTTP POST integration with custom headers
  • 🖥️ Real-time HTML dashboard with auto-refresh
  • 🔐 API key authentication
  • ⚡ Timeout management and error handling
  • 🎨 Beautiful gradient UI with color-coded events
  • 📊 Event aggregation and visualization

Quick Start:

# Start the server
cd examples/custom-backend
bun server.ts

# Configure hook in .claude/settings.json
{
  "hooks": {
    "SessionStart": [
      { "type": "command", "command": "bun /path/to/examples/custom-backend/hook.ts" }
    ]
  }
}

# View dashboard at http://localhost:3030

See examples/custom-backend/README.md for complete documentation, testing instructions, and production deployment guide.

Transaction Logger - Flight Recorder for Conversations

Comprehensive transaction tracker that captures the full context of each conversation turn:

👉 examples/transaction-logger/ - Complete transaction tracking system

What It Tracks:

  • 💬 User prompts (all messages in the turn)
  • 🛠️ All tool calls with inputs
  • 📝 File changes (Write/Edit/MultiEdit with line counts)
  • ✅ Todos created/updated
  • 🤖 Assistant responses
  • 📊 Transaction metadata (IDs, timestamps, duration, line numbers)

Features:

  • Uses transaction_id to group all events in a turn
  • Writes to .claude/logs/transactions.jsonl
  • Beautiful TUI viewer with color-coding
  • Filter by session, show latest N, watch mode
  • Overall statistics and analytics
  • Perfect for debugging, auditing, and understanding workflows

Quick Start:

// Configure in .claude/settings.json (UserPromptSubmit, PostToolUse, Stop hooks)
// Then view transactions:
bun examples/transaction-logger/viewer.ts --latest 10
bun examples/transaction-logger/viewer.ts --watch

Example Transaction:

{
  "transaction_id": "a3f2c1d8-...",
  "session_name": "brave-elephant",
  "user_prompts": ["Help me implement auth"],
  "files_changed": [{"path": "auth.ts", "operation": "write", "lines_changed": 120}],
  "todos_created": [{"content": "Create service", "status": "completed"}],
  "assistant_response": "I've created the authentication service...",
  "transcript_line_number": 42,
  "summary": {
    "total_files_changed": 3,
    "total_todos_created": 5,
    "unique_tools": ["Write", "Edit", "TodoWrite"]
  }
}

See examples/transaction-logger/README.md for complete documentation, analytics examples, and integration ideas.

Type Definitions

All hook events are fully typed:

import type {
  // Input types
  PreToolUseInput,
  PostToolUseInput,
  SessionStartInput,
  // ... all other events

  // Output types
  PreToolUseOutput,
  PostToolUseOutput,
  // ...

  // Context
  HookContext,
  TranscriptLine,
} from 'claude-hooks-sdk';

Hook Events

| Event | When It Fires | Common Use Cases | |-------|---------------|------------------| | PreToolUse | Before tool execution | Validation, blocking, security | | PostToolUse | After tool completion | Formatting, testing, logging | | Notification | On Claude notifications | Custom notifications | | UserPromptSubmit | Before processing input | Context injection, validation | | Stop | Agent finishes responding | Cleanup, analytics | | SubagentStop | Subagent completes | Delegation tracking | | PreCompact | Before context compaction | State preservation | | SessionStart | Session begins | Initialization, setup | | SessionEnd | Session terminates | Cleanup, reporting |

Advanced Usage

Multiple Handlers

You can register multiple handlers for the same event:

manager
  .onPreToolUse(validateSecurity)
  .onPreToolUse(logToolUse)
  .onPreToolUse(checkRateLimit);

Handlers execute in registration order and can stop the chain by returning exitCode: 2.

Conditional Execution

manager.onPostToolUse(async (input) => {
  // Only process Write tool
  if (input.tool_name !== 'Write') {
    return success();
  }

  const filePath = input.tool_input.file_path;

  // Format TypeScript files
  if (filePath.endsWith('.ts') || filePath.endsWith('.tsx')) {
    await formatFile(filePath);
  }

  return success();
});

Error Handling

manager.onSessionStart(async (input) => {
  try {
    await initializeServices();
    return success();
  } catch (err) {
    return error(`Initialization failed: ${err.message}`);
  }
});

Development

# Install dependencies
bun install

# Build
bun run build

# Type check
bun run typecheck

# Run examples
bun examples/basic-hook.ts

License

MIT © hgeldenhuys

Contributing

Contributions welcome! Please read our contributing guidelines first.

Resources

Related Projects

This is the standalone, zero-dependency version of the SDK. For platform-specific integrations, check the repository for additional tooling.