All skills
anthropics avatar

/hook-development

@387dc35
by Anthropicanthropics/claude-code149k stars
25,117

This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.

Use this Skill: https://skilld.dev/gh/anthropics/claude-code/hook-development

This session only. Nothing lands on disk.

referencesmigration.md

≈2.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Migrating from Basic to Advanced Hooks

This guide shows how to migrate from basic command hooks to advanced prompt-based hooks for better maintainability and flexibility.

Why Migrate?

Prompt-based hooks offer several advantages:

  • Natural language reasoning: LLM understands context and intent
  • Better edge case handling: Adapts to unexpected scenarios
  • No bash scripting required: Simpler to write and maintain
  • More flexible validation: Can handle complex logic without coding

Migration Example: Bash Command Validation

Before (Basic Command Hook)

Configuration:

{
  "PreToolUse": [
    {
      "matcher": "Bash",
      "hooks": [
        {
          "type": "command",
          "command": "bash validate-bash.sh"
        }
      ]
    }
  ]
}

Script (validate-bash.sh):

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

# Hard-coded validation logic
if [[ "$command" == *"rm -rf"* ]]; then
  echo "Dangerous command detected" >&2
  exit 2
fi

Problems:

  • Only checks for exact "rm -rf" pattern
  • Doesn't catch variations like rm -fr or rm -r -f
  • Misses other dangerous commands (dd, mkfs, etc.)
  • No context awareness
  • Requires bash scripting knowledge

After (Advanced Prompt Hook)

Configuration:

{
  "PreToolUse": [
    {
      "matcher": "Bash",
      "hooks": [
        {
          "type": "prompt",
          "prompt": "Command: $TOOL_INPUT.command. Analyze for: 1) Destructive operations (rm -rf, dd, mkfs, etc) 2) Privilege escalation (sudo) 3) Network operations without user consent. Return 'approve' or 'deny' with explanation.",
          "timeout": 15
        }
      ]
    }
  ]
}

Benefits:

  • Catches all variations and patterns
  • Understands intent, not just literal strings
  • No script file needed
  • Easy to extend with new criteria
  • Context-aware decisions
  • Natural language explanation in denial

Migration Example: File Write Validation

Before (Basic Command Hook)

Configuration:

{
  "PreToolUse": [
    {
      "matcher": "Write",
      "hooks": [
        {
          "type": "command",
          "command": "bash validate-write.sh"
        }
      ]
    }
  ]
}

Script (validate-write.sh):

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

# Check for path traversal
if [[ "$file_path" == *".."* ]]; then
  echo '{"decision": "deny", "reason": "Path traversal detected"}' >&2
  exit 2
fi

# Check for system paths
if [[ "$file_path" == "/etc/"* ]] || [[ "$file_path" == "/sys/"* ]]; then
  echo '{"decision": "deny", "reason": "System file"}' >&2
  exit 2
fi

Problems:

  • Hard-coded path patterns
  • Doesn't understand symlinks
  • Missing edge cases (e.g., /etc vs /etc/)
  • No consideration of file content

After (Advanced Prompt Hook)

Configuration:

{
  "PreToolUse": [
    {
      "matcher": "Write|Edit",
      "hooks": [
        {
          "type": "prompt",
          "prompt": "File path: $TOOL_INPUT.file_path. Content preview: $TOOL_INPUT.content (first 200 chars). Verify: 1) Not system directories (/etc, /sys, /usr) 2) Not credentials (.env, tokens, secrets) 3) No path traversal 4) Content doesn't expose secrets. Return 'approve' or 'deny'."
        }
      ]
    }
  ]
}

Benefits:

  • Context-aware (considers content too)
  • Handles symlinks and edge cases
  • Natural understanding of "system directories"
  • Can detect secrets in content
  • Easy to extend criteria

When to Keep Command Hooks

Command hooks still have their place:

1. Deterministic Performance Checks

#!/bin/bash
# Check file size quickly
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
size=$(stat -f%z "$file_path" 2>/dev/null || stat -c%s "$file_path" 2>/dev/null)

if [ "$size" -gt 10000000 ]; then
  echo '{"decision": "deny", "reason": "File too large"}' >&2
  exit 2
fi

Use command hooks when: Validation is purely mathematical or deterministic.

2. External Tool Integration

#!/bin/bash
# Run security scanner
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
scan_result=$(security-scanner "$file_path")

if [ "$?" -ne 0 ]; then
  echo "Security scan failed: $scan_result" >&2
  exit 2
fi

Use command hooks when: Integrating with external tools that provide yes/no answers.

3. Very Fast Checks (< 50ms)

#!/bin/bash
# Quick regex check
command=$(echo "$input" | jq -r '.tool_input.command')

if [[ "$command" =~ ^(ls|pwd|echo)$ ]]; then
  exit 0  # Safe commands
fi

Use command hooks when: Performance is critical and logic is simple.

Hybrid Approach

Combine both for multi-stage validation:

{
  "PreToolUse": [
    {
      "matcher": "Bash",
      "hooks": [
        {
          "type": "command",
          "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/quick-check.sh",
          "timeout": 5
        },
        {
          "type": "prompt",
          "prompt": "Deep analysis of bash command: $TOOL_INPUT",
          "timeout": 15
        }
      ]
    }
  ]
}

The command hook does fast deterministic checks, while the prompt hook handles complex reasoning.

Migration Checklist

When migrating hooks:

  • Identify the validation logic in the command hook
  • Convert hard-coded patterns to natural language criteria
  • Test with edge cases the old hook missed
  • Verify LLM understands the intent
  • Set appropriate timeout (usually 15-30s for prompt hooks)
  • Document the new hook in README
  • Remove or archive old script files

Migration Tips

  1. Start with one hook: Don't migrate everything at once
  2. Test thoroughly: Verify prompt hook catches what command hook caught
  3. Look for improvements: Use migration as opportunity to enhance validation
  4. Keep scripts for reference: Archive old scripts in case you need to reference the logic
  5. Document reasoning: Explain why prompt hook is better in README

Complete Migration Example

Original Plugin Structure

my-plugin/
├── .claude-plugin/plugin.json
├── hooks/hooks.json
└── scripts/
    ├── validate-bash.sh
    ├── validate-write.sh
    └── check-tests.sh

After Migration

my-plugin/
├── .claude-plugin/plugin.json
├── hooks/hooks.json      # Now uses prompt hooks
└── scripts/              # Archive or delete
    └── archive/
        ├── validate-bash.sh
        ├── validate-write.sh
        └── check-tests.sh

Updated hooks.json

{
  "PreToolUse": [
    {
      "matcher": "Bash",
      "hooks": [
        {
          "type": "prompt",
          "prompt": "Validate bash command safety: destructive ops, privilege escalation, network access"
        }
      ]
    },
    {
      "matcher": "Write|Edit",
      "hooks": [
        {
          "type": "prompt",
          "prompt": "Validate file write safety: system paths, credentials, path traversal, content secrets"
        }
      ]
    }
  ],
  "Stop": [
    {
      "matcher": "*",
      "hooks": [
        {
          "type": "prompt",
          "prompt": "Verify tests were run if code was modified"
        }
      ]
    }
  ]
}

Result: Simpler, more maintainable, more powerful.

Common Migration Patterns

Pattern: String Contains → Natural Language

Before:

if [[ "$command" == *"sudo"* ]]; then
  echo "Privilege escalation" >&2
  exit 2
fi

After:

"Check for privilege escalation (sudo, su, etc)"

Pattern: Regex → Intent

Before:

if [[ "$file" =~ \.(env|secret|key|token)$ ]]; then
  echo "Credential file" >&2
  exit 2
fi

After:

"Verify not writing to credential files (.env, secrets, keys, tokens)"

Pattern: Multiple Conditions → Criteria List

Before:

if [ condition1 ] || [ condition2 ] || [ condition3 ]; then
  echo "Invalid" >&2
  exit 2
fi

After:

"Check: 1) condition1 2) condition2 3) condition3. Deny if any fail."

Conclusion

Migrating to prompt-based hooks makes plugins more maintainable, flexible, and powerful. Reserve command hooks for deterministic checks and external tool integration.

Source: SKILL.md on GitHub

1 warning5mo5 checks · Risk SAFE
  • Gen Agent Trust Hub7mo

    The skill provides utility scripts and documentation for developing Claude Code hooks, including a linter and validator to prevent common security issues. A low-risk surface for indirect prompt injection is identified in the documented prompt-based hook patterns where untrusted tool input is interpolated into prompts without sanitization or boundaries.

  • Socket6mo

    No alerts

  • Snyk7mo

    Risk: LOW · No issues

  • Runlayer7mo

    3/11 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 387dc35. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 17 hours ago.

Activeupdated 11 months ago
version
0.1.0

README badge

README badge for anthropics/claude-code/hook-development