Back to Skills

creating-hooks

majiayu000
Updated Today
58
9
58
View on GitHub
Metaaiautomationdesign

About

This skill provides a comprehensive guide for implementing Claude Code hooks to create event-driven automation. It covers all hook events, matchers, exit codes, and environment variables for use cases like auto-linting, validation, and context injection. Developers should use it when building automation that triggers at specific workflow points like PreToolUse or PostToolUse.

Quick Install

Claude Code

Recommended
Plugin CommandRecommended
/plugin add https://github.com/majiayu000/claude-skill-registry
Git CloneAlternative
git clone https://github.com/majiayu000/claude-skill-registry.git ~/.claude/skills/creating-hooks

Copy and paste this command in Claude Code to install this skill

Documentation

Creating Hooks

Build event-driven automation for Claude Code using hooks - scripts that execute at specific workflow points.

Quick Reference

Hook EventWhen It FiresUses MatcherCommon Use Cases
PreToolUseBefore tool executesYes (tool name)Validation, auto-approval, input modification
PostToolUseAfter tool succeedsYes (tool name)Auto-formatting, linting, logging
PostToolUseFailureAfter tool failsYes (tool name)Error handling, fallback logic
PermissionRequestUser shown permission dialogYes (tool name)Auto-allow/deny, policy enforcement
NotificationClaude sends notificationYes (type)Custom alerts, logging
UserPromptSubmitUser submits promptNoPrompt validation, context injection
Setup--init or --maintenanceYes (trigger)Dependency install, migrations, cleanup
StopMain agent finishesNoTask completion checks, force continue
SubagentStartSubagent (Task) spawnsNoLogging, tracking, rate limiting
SubagentStopSubagent (Task) finishesNoSubagent task validation
PreCompactBefore context compactionYes (trigger)Custom compaction handling
SessionStartSession begins/resumesYes (source)Context loading, env setup
SessionEndSession endsYes (reason)Cleanup, logging

Updated for Claude Code 2.1.17

Configuration Locations

Hooks are configured in settings files (in order of precedence):

LocationScopeCommitted
~/.claude/settings.jsonUser (all projects)No
.claude/settings.jsonProjectYes
.claude/settings.local.jsonLocal projectNo
Enterprise managed policyOrganizationYes

Hook Structure

{
  "hooks": {
    "EventName": [
      {
        "matcher": "ToolPattern",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Matcher Syntax

PatternMatchesExample
WriteExact tool nameOnly Write tool
Edit|WriteRegex OREdit or Write
Notebook.*Regex wildcardNotebookEdit, NotebookRead
mcp__memory__.*MCP server toolsAll memory server tools
* or ""All toolsAny tool

Note: Matchers are case-sensitive and only apply to PreToolUse, PostToolUse, and PermissionRequest.

Hook Types

TypeDescriptionKey Field
commandExecute bash scriptcommand: bash command to run
promptLLM-based evaluationprompt: prompt text for Haiku

Hook Options

OptionTypeDescription
timeoutnumberTimeout in seconds (default: 60, max: 600 as of 2.1.3)
oncebooleanRun only once per session (frontmatter hooks only)

Note: As of 2.1.3, the maximum hook timeout was increased from 60 seconds to 10 minutes (600s).

Exit Codes

Exit CodeMeaningBehavior
0SuccessContinue normally. stdout parsed for JSON control
2Blocking errorBlock action. stderr shown to Claude
OtherNon-blocking errorLog warning. Continue normally

Exit Code 2 Behavior by Event

EventExit Code 2 Effect
PreToolUseBlocks tool call, stderr to Claude
PermissionRequestDenies permission, stderr to Claude
PostToolUsestderr to Claude (tool already ran)
UserPromptSubmitBlocks prompt, erases it, stderr to user
Stop / SubagentStopBlocks stoppage, stderr to Claude
Notification / SessionStart / SessionEnd / PreCompactstderr to user only

Environment Variables

VariableDescriptionAvailable In
CLAUDE_PROJECT_DIRAbsolute path to project rootAll hooks
CLAUDE_PLUGIN_ROOTAbsolute path to plugin directoryPlugin hooks only
CLAUDE_ENV_FILEFile path for persisting env varsSessionStart only
CLAUDE_CODE_REMOTE"true" if running in web environmentAll hooks

Hook Input (stdin)

All hooks receive JSON via stdin with common fields:

{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/current/directory",
  "permission_mode": "default",
  "hook_event_name": "EventName"
}

Permission modes: default, plan, acceptEdits, dontAsk, bypassPermissions

Decision Guide: Which Hook Do I Need?

Before Tool Execution

Use PreToolUse to:

  • Validate tool inputs before execution
  • Auto-approve safe operations (e.g., reading docs)
  • Block dangerous commands
  • Modify tool inputs

After Tool Execution

Use PostToolUse to:

  • Auto-format code after Write/Edit
  • Run linters after file changes
  • Log file modifications
  • Provide feedback to Claude

Permission Automation

Use PermissionRequest to:

  • Auto-allow trusted operations
  • Auto-deny blocked patterns
  • Enforce security policies

Prompt Processing

Use UserPromptSubmit to:

  • Inject context (current time, git status)
  • Validate prompts for secrets
  • Block sensitive requests

Session Lifecycle

Use SessionStart to:

  • Load development context
  • Set environment variables
  • Install dependencies

Use SessionEnd to:

  • Clean up resources
  • Log session statistics

Agent Completion

Use Stop / SubagentStop to:

  • Verify task completion
  • Force Claude to continue working
  • Add completion checks

Context Management

Use PreCompact to:

  • Customize compaction behavior
  • Add pre-compaction context

Alerts

Use Notification to:

  • Custom notification routing
  • Third-party integrations (Slack, Discord)

Workflow: Creating a Hook

Prerequisites

  • Identify which event to hook into
  • Decide: command (bash) or prompt (LLM) type
  • Plan exit code behavior

Steps

  1. Create hook script

    • Write executable script (bash, python, etc.)
    • Read JSON from stdin
    • Output JSON to stdout (if needed)
    • Use appropriate exit code
  2. Configure in settings

    • Add to appropriate settings file
    • Set matcher pattern (if applicable)
    • Set timeout if needed (default: 60s)
  3. Test

    • Run claude --debug to see hook execution
    • Check /hooks menu for registration
    • Verify exit codes work as expected

Validation

  • Script is executable (chmod +x)
  • JSON input/output is valid
  • Exit codes are correct
  • Matcher pattern works

Tool-Specific Hooks

Common patterns for hooks targeting specific tools.

Bash Tool Hooks

Validate commands before execution, log sensitive operations, or block dangerous commands.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate-bash.sh"
          }
        ]
      }
    ]
  }
}

Common validations:

  • Block rm -rf / patterns
  • Require approval for sudo commands
  • Log all commands to audit file
  • Block network commands in certain contexts

Write Tool Hooks

Validate file paths, enforce naming conventions, or auto-format after write.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/after-write.sh"
          }
        ]
      }
    ]
  }
}

Common patterns:

  • Auto-format with Prettier/Black
  • Validate file encoding (UTF-8)
  • Check for accidental credential writes
  • Run type-checking after TypeScript writes

Edit Tool Hooks

Validate edits, prevent changes to critical files, or run linting after edits.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate-edit.sh"
          }
        ]
      }
    ]
  }
}

Common patterns:

  • Block edits to lock files (package-lock.json)
  • Prevent edits to generated files
  • Run linter after file edits
  • Validate imports/exports after module changes

Read Tool Hooks

Log file access, validate read permissions, or inject context based on files read.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate-read.sh"
          }
        ]
      }
    ]
  }
}

Common patterns:

  • Block reading sensitive files (.env, credentials)
  • Log file access for auditing
  • Auto-approve reading documentation
  • Inject related context when reading specific files

Common Patterns

Auto-Format on File Write

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

Inject Context on Session Start

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo \"Git branch: $(git branch --show-current)\""
          }
        ]
      }
    ]
  }
}

Auto-Approve Documentation Reads

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/approve-docs.py"
          }
        ]
      }
    ]
  }
}

Hook Framework (YAML Configuration)

For projects with multiple hooks, the Hook Framework provides a YAML-based configuration with built-in handlers and environment variable injection.

Installation

bun add claude-code-sdk

YAML Configuration

Create hooks.yaml in your project root:

version: 1

settings:
  debug: false
  parallelExecution: true
  defaultTimeoutMs: 30000

builtins:
  # Human-friendly session names (e.g., "brave-elephant")
  session-naming:
    enabled: true
    options:
      format: adjective-animal

  # Track turns between Stop events
  turn-tracker:
    enabled: true

  # Block dangerous Bash commands
  dangerous-command-guard:
    enabled: true
    options:
      blockedPatterns:
        - "rm -rf /"
        - "rm -rf ~"

  # Inject session context
  context-injection:
    enabled: true
    options:
      template: "Session: ${sessionName} | Turn: ${turnId}"

  # Log tool usage
  tool-logger:
    enabled: true
    options:
      outputPath: ~/.claude/logs/tools.log

handlers:
  # Custom command handlers
  my-validator:
    events: [PreToolUse]
    matcher: "Bash"
    command: ./scripts/validate-command.sh
    timeoutMs: 5000

Built-in Handlers

HandlerDescriptionDefault Events
session-namingAssigns human-friendly namesSessionStart
turn-trackerTracks turns between Stop eventsSessionStart, Stop, SubagentStop
dangerous-command-guardBlocks dangerous Bash commandsPreToolUse
context-injectionInjects session/turn contextSessionStart, PreCompact
tool-loggerLogs tool usage with contextPostToolUse
event-loggerLogs all hook events to JSONL for indexingAll events
debug-loggerFull payload logging for debuggingAll events
metricsRecords hook execution timing metricsAll events

Environment Variables for Custom Handlers

Custom command handlers receive these environment variables:

VariableDescription
CLAUDE_SESSION_IDCurrent session ID
CLAUDE_SESSION_NAMEHuman-friendly session name
CLAUDE_TURN_IDTurn identifier (session:sequence)
CLAUDE_TURN_SEQUENCECurrent turn number
CLAUDE_EVENT_TYPEHook event type
CLAUDE_CWDCurrent working directory
CLAUDE_PROJECT_DIRProject root path

TypeScript Framework

import { createFramework, handler, blockResult } from 'claude-code-sdk/hooks/framework';

const framework = createFramework({ debug: true });

// Block dangerous commands
framework.onPreToolUse(
  handler()
    .id('danger-guard')
    .forTools('Bash')
    .handle(ctx => {
      const input = ctx.event.tool_input as { command?: string };
      if (input.command?.includes('rm -rf /')) {
        return blockResult('Dangerous command blocked');
      }
      return { success: true };
    })
);

// Access turn/session context
framework.onPostToolUse(
  handler()
    .id('context-logger')
    .handle(ctx => {
      const turnId = ctx.results.get('turn-tracker')?.data?.turnId;
      const sessionName = ctx.results.get('session-naming')?.data?.sessionName;
      console.error(`[${sessionName}] Turn ${turnId}: ${ctx.event.tool_name}`);
      return { success: true };
    })
);

await framework.run();

Using with settings.json

Point your settings.json to the framework entry point:

{
  "hooks": {
    "PreToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "bun run hooks-framework" }] }],
    "PostToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "bun run hooks-framework" }] }],
    "SessionStart": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "bun run hooks-framework" }] }],
    "Stop": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "bun run hooks-framework" }] }]
  }
}

Debugging

IssueSolution
Hook not runningCheck /hooks menu, verify JSON syntax
Wrong matcherTool names are case-sensitive
Command not foundUse absolute paths or $CLAUDE_PROJECT_DIR
Script not executingCheck permissions (chmod +x)
Exit code ignoredOnly 0, 2, and other are recognized
Framework not loadingCheck hooks.yaml syntax, run with debug: true

Run with debug mode:

claude --debug

Security Considerations

  • Validate and sanitize all inputs
  • Quote shell variables ("$VAR" not $VAR)
  • Check for path traversal (..)
  • Use absolute paths for scripts
  • Skip sensitive files (.env, keys)

Frontmatter Hooks

Hooks can also be defined directly in YAML frontmatter of Skills, Agents, and Slash Commands. These hooks are:

  • Lifecycle-scoped - Only active while the component executes
  • Auto-cleanup - Removed when the component finishes
  • Portable - Packaged with the component for distribution

Supported events: PreToolUse, PostToolUse, Stop

Quick Example (in a Skill)

---
name: my-skill
description: A skill with lifecycle hooks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./validate.sh"
          once: true
---

Key Differences from Settings Hooks

AspectSettings HooksFrontmatter Hooks
Locationsettings.jsonSkill/Agent/Command YAML
ScopeGlobal or projectComponent lifecycle
EventsAll 10 eventsPreToolUse, PostToolUse, Stop
CleanupManualAutomatic
once optionNoYes

See FRONTMATTER-HOOKS.md for complete documentation.

Reference Files

FileContents
EVENTS.mdDetailed event documentation with input/output schemas
EXAMPLES.mdComplete working examples
FRONTMATTER-HOOKS.mdFrontmatter hooks in skills, agents, commands
TROUBLESHOOTING.mdCommon issues and solutions

GitHub Repository

majiayu000/claude-skill-registry
Path: skills/creating-hooks

Related Skills

content-collections

Meta

This skill provides a production-tested setup for Content Collections, a TypeScript-first tool that transforms Markdown/MDX files into type-safe data collections with Zod validation. Use it when building blogs, documentation sites, or content-heavy Vite + React applications to ensure type safety and automatic content validation. It covers everything from Vite plugin configuration and MDX compilation to deployment optimization and schema validation.

View skill

creating-opencode-plugins

Meta

This skill provides the structure and API specifications for creating OpenCode plugins that hook into 25+ event types like commands, files, and LSP operations. It offers implementation patterns for JavaScript/TypeScript modules that intercept and extend the AI assistant's lifecycle. Use it when you need to build event-driven plugins for monitoring, custom handling, or extending OpenCode's capabilities.

View skill

sglang

Meta

SGLang is a high-performance LLM serving framework that specializes in fast, structured generation for JSON, regex, and agentic workflows using its RadixAttention prefix caching. It delivers significantly faster inference, especially for tasks with repeated prefixes, making it ideal for complex, structured outputs and multi-turn conversations. Choose SGLang over alternatives like vLLM when you need constrained decoding or are building applications with extensive prefix sharing.

View skill

evaluating-llms-harness

Testing

This Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.

View skill