Files
claude-plugins/claude-code/skills/claude-hooks/SKILL.md
2025-10-28 12:28:48 -05:00

8.0 KiB

name, description
name description
Claude Code Hooks Configure event-driven hooks for Claude Code to automate workflows, validate code, inject context, and control tool execution. Use PROACTIVELY when setting up automation, enforcing standards, integrating external tools, or when users mention "automatically run", "on save", "event-driven", "workflow automation", or "enforce rules". NOT for one-time scripts.

Claude Code Hooks

When to Use This Skill

Use this skill when:

  • Automating workflows with event-driven triggers
  • Enforcing code standards or security policies
  • Validating changes before/after tool execution
  • Injecting context at session start
  • Logging or monitoring tool usage
  • Setting up team-wide automation

Do NOT use this skill for:

  • Creating slash commands (use claude-command-expert skill)
  • Building full plugins (use claude-plugin skill)
  • One-time scripts (just run them directly)

Quick Start

1. Hook Configuration File

# Project-wide (team shared)
.claude/settings.json

# User-specific (not shared)
.claude/settings.local.json

# Global (all projects)
~/.claude/settings.json

2. Simple Example - Log File Changes

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$(date): Modified $TOOL_ARGS\" >> .claude/changes.log"
          }
        ]
      }
    ]
  }
}

Hook Types

Event When It Runs Common Use
SessionStart Session begins Inject project context
SessionEnd Session ends Cleanup, backups
PreToolUse Before tool execution Validation, permission checks
PostToolUse After tool completes Formatting, linting
UserPromptSubmit User submits prompt Logging, analytics
Notification Claude sends notification Desktop alerts
Stop Agent finishes responding Post-response processing

Core Concepts

Matcher Patterns

// Single tool
"matcher": "Write"

// Multiple tools (OR)
"matcher": "Write|Edit|Read"

// All tools
"matcher": "*"

// Git operations
"matcher": "Bash(git:*)"

Exit Codes

# Success - Continue
exit 0

# Blocking Error - Stop operation
exit 2

# Non-Blocking Error - Continue
exit 1

Environment Variables

$CLAUDE_PROJECT_DIR   # Current project directory
$TOOL_NAME           # Name of the tool being used
$TOOL_ARGS           # Arguments passed to the tool
$HOOK_EVENT          # Event type (PreToolUse, etc.)

Common Use Cases

Auto-Format on Save

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "cd $CLAUDE_PROJECT_DIR && npx prettier --write $TOOL_ARGS && exit 0"
          }
        ]
      }
    ]
  }
}

Security Validation

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "if grep -qiE '(password|api[_-]?key|secret)' $TOOL_ARGS 2>/dev/null; then echo 'Error: Possible secret detected' >&2; exit 2; fi; exit 0",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Test Before Commit

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "cd $CLAUDE_PROJECT_DIR && npm test -- --silent || (echo 'Tests failed' >&2; exit 2)",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

For complete examples, see examples.md

Hook Configuration

Basic Structure

{
  "type": "command",
  "command": "your-command-here",
  "timeout": 60  // Optional, default 60 seconds
}

Multiple Hooks

Run several hooks in sequence:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write $TOOL_ARGS"
          },
          {
            "type": "command",
            "command": "npx eslint --fix $TOOL_ARGS"
          },
          {
            "type": "command",
            "command": "git add $TOOL_ARGS"
          }
        ]
      }
    ]
  }
}

Conditional Execution

{
  "type": "command",
  "command": "if [[ $TOOL_ARGS == *.js ]]; then npm run format $TOOL_ARGS; fi; exit 0"
}

Settings File Hierarchy

Load Order (highest to lowest priority):

  1. .claude/settings.local.json - Project, user-only (gitignored)
  2. .claude/settings.json - Project, team-shared (in git)
  3. ~/.claude/settings.json - Global, user-only

Use Cases:

  • Global: Personal preferences, universal logging
  • Project: Team standards, project automation
  • Local: Personal overrides, development experiments

Testing Hooks

Start Simple

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Hook triggered for: $TOOL_ARGS' && exit 0"
          }
        ]
      }
    ]
  }
}

Test Exit Codes

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'This should block' >&2 && exit 2"
          }
        ]
      }
    ]
  }
}

Try writing a file - should be blocked.

Troubleshooting

Problem Solution
Hook not triggering Check matcher pattern, restart Claude Code
Hook fails silently Check exit codes (use 0 for success)
Command not found Use full path: /usr/local/bin/prettier
Permission denied chmod +x .claude/hooks/script.sh
Timeout Increase timeout value or optimize command

For detailed troubleshooting, see troubleshooting.md

Best Practices

1. Always Exit Explicitly

# Good
command && exit 0

# Bad
command

2. Use Timeouts

{
  "command": "npm test",
  "timeout": 120  // Don't let tests run forever
}

3. Handle Errors Gracefully

if [ -f "$TOOL_ARGS" ]; then
    process_file "$TOOL_ARGS"
else
    echo "File not found" >&2
fi
exit 0  # Don't block on missing file

4. Use Scripts for Complex Logic

// Bad - complex bash in JSON
{
  "command": "if [ -f $TOOL_ARGS ]; then cat $TOOL_ARGS | grep pattern | wc -l; fi"
}

// Good - external script
{
  "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-file.sh $TOOL_ARGS"
}

5. Test Before Team Deployment

Test in .claude/settings.local.json before adding to .claude/settings.json.

Security Considerations

Validate Input

# Sanitize TOOL_ARGS
if [[ ! $TOOL_ARGS =~ ^[a-zA-Z0-9/_.-]+$ ]]; then
    echo "Invalid file path" >&2
    exit 2
fi

Limit Permissions

// Specific (good)
"matcher": "Write|Edit"

// Too broad (risky)
"matcher": "*"

Avoid Destructive Commands

# Dangerous
rm -rf $TOOL_ARGS

# Safer
if [[ -f "$TOOL_ARGS" ]] && [[ "$TOOL_ARGS" != "/" ]]; then
    rm "$TOOL_ARGS"
fi

Resources

Quick Reference Card

Common Patterns

// Format on save
"PostToolUse": [{"matcher": "Write|Edit", "hooks": [{"type": "command", "command": "prettier --write $TOOL_ARGS"}]}]

// Block secrets
"PreToolUse": [{"matcher": "Write", "hooks": [{"type": "command", "command": "grep -q 'secret' $TOOL_ARGS && exit 2 || exit 0"}]}]

// Log all activity
"PreToolUse": [{"matcher": "*", "hooks": [{"type": "command", "command": "echo \"$TOOL_NAME: $TOOL_ARGS\" >> .claude/log && exit 0"}]}]

Remember: Hooks are powerful automation tools. Start simple, test thoroughly, use exit codes properly to control flow.