claude-code-hooks

hgeldenhuys's avatarfrom hgeldenhuys

Guide for implementing Claude Code hooks - automated scripts that execute at specific workflow points. Use when building hooks, understanding hook events, or troubleshooting hook configuration.

⭐3stars🔀0forks📁View on GitHub🕐Updated Dec 15, 2025

When & Why to Use This Skill

This Claude skill provides a comprehensive guide and framework for implementing Claude Code hooks, enabling developers to execute automated Bash or TypeScript scripts at specific points in the AI's workflow. It allows for deep customization of the agent's behavior, including tool validation, automated formatting, and context enrichment, significantly enhancing developer productivity and operational safety.

Use Cases

  • Automated Code Formatting: Trigger tools like Prettier or ESLint automatically after Claude writes or edits code to ensure style consistency.
  • Security Guardrails: Implement PreToolUse hooks to scan and block dangerous shell commands (e.g., 'rm -rf') or unauthorized file access before execution.
  • Dynamic Context Injection: Use UserPromptSubmit hooks to automatically inject environment variables, session metadata, or project-specific documentation into the AI's context.
  • Environment Initialization: Automate project setup tasks such as 'npm install' or environment variable configuration whenever a new Claude session starts.
  • Custom Notifications: Integrate with external communication tools to send alerts or logs when specific tool actions or subagent tasks are completed.
nameclaude-code-hooks
descriptionGuide for implementing Claude Code hooks - automated scripts that execute at specific workflow points. Use when building hooks, understanding hook events, or troubleshooting hook configuration.

Claude Code Hooks Implementation Guide

Implement automated hooks that execute bash commands or TypeScript scripts in response to Claude Code events.

Quick Reference

9 Available Hook Events

  1. PreToolUse - Before tool execution (validation, blocking)
  2. PostToolUse - After tool success (formatting, testing)
  3. Notification - On Claude notifications
  4. UserPromptSubmit - Before processing user input (context injection)
  5. Stop - When main agent finishes responding
  6. SubagentStop - When subagent completes
  7. PreCompact - Before context compaction
  8. SessionStart - Session begins
  9. SessionEnd - Session ends

Hook Input (stdin JSON)

{
  "session_id": "...",
  "transcript_path": "...",
  "cwd": "/current/working/dir",
  "permission_mode": "default",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": {"file_path": "...", "content": "..."},
  "tool_response": "..."
}

Hook Output Mechanisms

Exit Codes:

  • 0 - Success (stdout → transcript, or context if UserPromptSubmit)
  • 2 - Blocking error (stderr → Claude feedback)
  • Other - Non-blocking error (shown to user)

JSON Output (stdout):

{
  "continue": true,
  "stopReason": "optional message",
  "suppressOutput": true,
  "decision": "allow|deny|ask",
  "hookSpecificOutput": {}
}

For TypeScript hooks, use the SDK for type safety and utilities:

bun add 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',
  trackEdits: true,
});

manager.onPreToolUse(async (input) => {
  if (input.tool_name === 'Bash' && input.tool_input.command.includes('rm -rf /')) {
    logger.warn('Blocked dangerous command');
    return block('Dangerous command blocked');
  }
  return success();
});

manager.run();

Common Use Cases

1. Code Formatting (PostToolUse)

Configuration (.claude/settings.json):

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-code.sh"
          }
        ]
      }
    ]
  }
}

Script (.claude/hooks/format-code.sh):

#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path')

if [[ "$FILE_PATH" =~ \.(ts|tsx|js|jsx)$ ]]; then
  npx prettier --write "$FILE_PATH" 2>&1
  echo "✓ Formatted: $FILE_PATH"
fi

exit 0

2. Security Validation (PreToolUse)

#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

# Block dangerous patterns
if [[ "$COMMAND" =~ (rm\ -rf\ /|\.\./) ]]; then
  echo '{"decision": "deny", "continue": false}' | jq
  echo "❌ Blocked: Dangerous command" >&2
  exit 2
fi

echo '{"decision": "allow", "continue": true}' | jq
exit 0

3. Context Injection (UserPromptSubmit)

Using SDK (recommended):

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

// ONE LINE - injects session ID and name into Claude's context
createUserPromptSubmitHook();

4. Environment Setup (SessionStart)

#!/bin/bash
if [ ! -d "node_modules" ]; then
  bun install
fi

if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo "export NODE_ENV=development" >> "$CLAUDE_ENV_FILE"
fi

echo "✓ Environment ready"
exit 0

Configuration Patterns

Project-Specific Hooks

Store in .claude/settings.json (project root):

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {"type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh"}
        ]
      }
    ]
  }
}

Global Hooks

Store in ~/.claude/settings.json (user home).

Troubleshooting

Hook Not Executing

  1. Check matcher pattern (case-sensitive)
  2. Verify script permissions: chmod +x script.sh
  3. Enable debug mode: claude --debug

Hook Blocking Execution

  1. Exit code 2 blocks, other codes don't
  2. {"decision": "deny"} blocks PreToolUse

Resources