Agent Skills: Hooks Builder

Create event-driven hooks for Claude Code automation. Use when the user wants to create hooks, automate tool validation, add pre/post processing, enforce security policies, or configure settings.json hooks. Triggers: create hook, build hook, PreToolUse, PostToolUse, event automation, tool validation, security hook

UncategorizedID: mike-coulbourn/claude-vibes/hooks-builder

Install this agent skill to your local

pnpm dlx add-skill https://github.com/mike-coulbourn/claude-vibes/tree/HEAD/plugins/vibes/skills/hooks-builder

Skill Files

Browse the full folder contents for hooks-builder.

Download Skill

Loading file tree…

plugins/vibes/skills/hooks-builder/SKILL.md

Skill Metadata

Name
hooks-builder
Description
"Create event-driven hooks for Claude Code automation. Use when the user wants to create hooks, automate tool validation, add pre/post processing, enforce security policies, or configure settings.json hooks. Triggers: create hook, build hook, PreToolUse, PostToolUse, event automation, tool validation, security hook"

Hooks Builder

A comprehensive guide for creating Claude Code hooks — event-driven automation that monitors and controls Claude's actions.

Quick Reference

The 10 Hook Events

| Event | When It Fires | Can Block? | Supports Matchers? | |-------|--------------|------------|-------------------| | PreToolUse | Before tool executes | YES | YES (tool names) | | PermissionRequest | Permission dialog shown | YES | YES (tool names) | | PostToolUse | After tool succeeds | No | YES (tool names) | | Notification | Claude sends notification | No | YES | | UserPromptSubmit | User submits prompt | YES | No | | Stop | Claude finishes responding | Can force continue | No | | SubagentStop | Subagent finishes | Can force continue | No | | PreCompact | Before context compaction | No | YES (manual/auto) | | SessionStart | Session begins | No | YES (startup/resume/clear/compact) | | SessionEnd | Session ends | No | No |

Exit Code Semantics

| Exit Code | Meaning | Effect | |-----------|---------|--------| | 0 | Success | stdout parsed as JSON for control | | 2 | Blocking error | VETO — stderr shown to Claude | | Other | Non-blocking error | stderr logged in debug mode |

Configuration Locations

~/.claude/settings.json          → Personal hooks (all projects)
.claude/settings.json            → Project hooks (team, committed)
.claude/settings.local.json      → Local overrides (not committed)

Essential Environment Variables

| Variable | Description | |----------|-------------| | $CLAUDE_PROJECT_DIR | Project root directory | | $CLAUDE_CODE_REMOTE | Remote/local indicator | | $CLAUDE_ENV_FILE | Environment persistence path (SessionStart) | | $CLAUDE_PLUGIN_ROOT | Plugin directory (plugin hooks) |

Key Commands

/hooks              # View active hooks
claude --debug      # Enable debug logging
chmod +x script.sh  # Make script executable

6-Phase Workflow

Phase 1: Requirements Gathering

Use AskUserQuestion to clarify:

  1. What event should trigger this hook?

    • Tool execution (Pre/Post/Permission) → PreToolUse, PostToolUse, PermissionRequest
    • User input → UserPromptSubmit
    • Response completion → Stop, SubagentStop
    • Session lifecycle → SessionStart, SessionEnd
    • Context management → PreCompact
    • Notifications → Notification
  2. What should happen when triggered?

    • Observe only (logging, metrics)
    • Block/allow based on conditions
    • Modify inputs before execution
    • Add context to prompts
    • Force continuation
  3. Should it block, modify, or just observe?

    • Observer: PostToolUse, Notification, SessionEnd (can't block)
    • Gatekeeper: PreToolUse, PermissionRequest, UserPromptSubmit (can block)
    • Transformer: PreToolUse with updatedInput (can modify)
    • Controller: Stop, SubagentStop (can force continue)
  4. What are the security implications?

    • Will it handle untrusted input?
    • Could it expose sensitive data?
    • Does it need to access external systems?

Phase 2: Event Selection

Match event to use case:

| Use Case | Best Event | |----------|-----------| | Block dangerous operations | PreToolUse | | Auto-format code after writes | PostToolUse | | Validate user prompts | UserPromptSubmit | | Setup environment | SessionStart | | Ensure task completion | Stop | | Log all tool usage | PostToolUse with "*" matcher | | Protect sensitive files | PreToolUse for Write/Edit | | Add project context | UserPromptSubmit |

Determine if matchers are needed:

  • Specific tools? → Use matcher: "Write|Edit"
  • All tools? → Use "*" or omit matcher
  • MCP tools? → Use mcp__server__tool pattern
  • Bash commands? → Use Bash(git:*) pattern

Phase 3: Matcher Design

Matcher Pattern Syntax:

// Exact match (case-sensitive!)
"matcher": "Write"

// OR pattern
"matcher": "Write|Edit"

// Prefix match
"matcher": "Notebook.*"

// Contains match
"matcher": ".*Read.*"

// All tools
"matcher": "*"

// MCP tools
"matcher": "mcp__memory__.*"

// Bash sub-patterns
"matcher": "Bash(git:*)"

Common Matcher Patterns:

| Pattern | Matches | |---------|---------| | "Write" | Only Write tool | | "Write\|Edit" | Write OR Edit | | "Bash" | All Bash commands | | "Bash(git:*)" | Only git commands | | "Bash(npm:*)" | Only npm commands | | "mcp__.*__.*" | All MCP tools | | ".*" or "*" | Everything |

Phase 4: Implementation

Choose implementation approach:

  1. Inline command (simple, no external file):

    {
      "type": "command",
      "command": "echo \"$(date) | $tool_name\" >> ~/.claude/audit.log"
    }
    
  2. External script (complex logic, reusable):

    {
      "type": "command",
      "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate.sh"
    }
    
  3. Prompt-based (LLM evaluation, intelligent decisions):

    {
      "type": "prompt",
      "prompt": "Analyze if all tasks are complete: $ARGUMENTS",
      "timeout": 30
    }
    

Script Template (Bash):

#!/bin/bash
set -euo pipefail

# Read JSON input from stdin
input=$(cat)

# Parse fields with jq
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')

# Your logic here
if [[ "$file_path" == *".env"* ]]; then
    echo "BLOCKED: Cannot modify .env files" >&2
    exit 2
fi

# Success - output decision
echo '{"decision": "approve"}'
exit 0

Script Template (Python):

#!/usr/bin/env python3
import sys
import json

# Read JSON input from stdin
data = json.load(sys.stdin)

# Extract fields
tool_name = data.get('tool_name', '')
tool_input = data.get('tool_input', {})
file_path = tool_input.get('file_path', '')

# Your logic here
if '.env' in file_path:
    print("BLOCKED: Cannot modify .env files", file=sys.stderr)
    sys.exit(2)

# Success - output decision
output = {"decision": "approve"}
print(json.dumps(output))
sys.exit(0)

Phase 5: Security Hardening

CRITICAL: Hooks execute shell commands with YOUR permissions.

Security Checklist:

  • [ ] All variables quoted: "$VAR" not $VAR
  • [ ] JSON parsed with jq or json.load (not grep/sed)
  • [ ] Paths validated (no .., normalized)
  • [ ] No sensitive data in logs/output
  • [ ] No sudo or privilege escalation
  • [ ] Script tested manually first
  • [ ] Project hooks audited before running
  • [ ] Timeout set appropriately
  • [ ] Error handling for all failure modes

Secure Patterns:

# UNSAFE - injection risk
rm $file_path

# SAFE - quoted, prevents flag injection
rm -- "$file_path"

# UNSAFE - parsing risk
cat "$input" | grep "field"

# SAFE - proper JSON parsing
echo "$input" | jq -r '.field'

Defense in Depth:

  1. Input validation (parse JSON properly)
  2. Path sanitization (normalize, check boundaries)
  3. Output sanitization (no sensitive data)
  4. Fail-safe defaults (block on error, not allow)
  5. Timeout protection (prevent infinite loops)

Phase 6: Testing

Step 1: Manual Script Testing

# Create mock input
cat > /tmp/mock-input.json << 'EOF'
{
  "session_id": "test-123",
  "hook_event_name": "PreToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.txt",
    "content": "test content"
  }
}
EOF

# Test script
cat /tmp/mock-input.json | ./my-hook.sh
echo "Exit code: $?"

Step 2: Edge Case Testing

  • Empty inputs: {}
  • Missing fields: {"tool_name": "Write"}
  • Malicious inputs: {"tool_input": {"file_path": "; rm -rf /"}}
  • Large inputs: 10KB+ content
  • Unicode: paths with special characters

Step 3: Integration Testing

# Start Claude with debug mode
claude --debug

# Trigger the tool your hook targets
# Watch debug output for hook execution

Step 4: Verification

# Check hooks are registered
/hooks

# Watch hook execution
claude --debug 2>&1 | grep -i hook

Hook Patterns

Observer Pattern

Log without blocking — use PostToolUse or Notification.

{
  "hooks": {
    "PostToolUse": [{
      "matcher": "*",
      "hooks": [{
        "type": "command",
        "command": "echo \"$(date) | $tool_name\" >> ~/.claude/audit.log"
      }]
    }]
  }
}

Gatekeeper Pattern

Block dangerous actions — use PreToolUse or PermissionRequest.

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{
        "type": "command",
        "command": "python3 ~/.claude/hooks/file-protector.py"
      }]
    }]
  }
}

Transformer Pattern

Modify inputs before execution — use PreToolUse with updatedInput.

# In script, output:
output = {
    "hookSpecificOutput": {
        "hookEventName": "PreToolUse",
        "permissionDecision": "allow",
        "updatedInput": {
            "content": add_license_header(original_content)
        }
    }
}
print(json.dumps(output))

Orchestrator Pattern

Coordinate multiple events — combine SessionStart + PreToolUse + PostToolUse.

{
  "hooks": {
    "SessionStart": [{
      "matcher": "startup",
      "hooks": [{"type": "command", "command": "~/.claude/hooks/setup-env.sh"}]
    }],
    "PreToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{"type": "command", "command": "~/.claude/hooks/validate.sh"}]
    }],
    "PostToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{"type": "command", "command": "~/.claude/hooks/format.sh"}]
    }]
  }
}

Common Pitfalls

1. Forgetting Exit Code 2 for Blocking

# WRONG - exit 1 doesn't block
echo "Error" >&2
exit 1

# RIGHT - exit 2 blocks Claude
echo "BLOCKED: reason" >&2
exit 2

2. Case Sensitivity in Matchers

// WRONG - won't match "Write" tool
"matcher": "write"

// RIGHT - case-sensitive match
"matcher": "Write"

3. Unquoted Variables (Injection Risk)

# WRONG - command injection vulnerability
rm $file_path

# RIGHT - properly quoted
rm -- "$file_path"

4. Missing Shebang in Scripts

# WRONG - no shebang, may fail
set -euo pipefail

# RIGHT - explicit interpreter
#!/bin/bash
set -euo pipefail

5. Not Making Scripts Executable

# Don't forget!
chmod +x ~/.claude/hooks/my-hook.sh

6. Forgetting to Quote Paths in JSON

// WRONG - spaces in path will break
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/script.sh"

// RIGHT - quoted path
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/script.sh"

7. No Error Handling

# WRONG - silent failures
input=$(cat)
tool=$(echo "$input" | jq -r '.tool_name')

# RIGHT - handle errors
input=$(cat) || { echo "Failed to read input" >&2; exit 1; }
tool=$(echo "$input" | jq -r '.tool_name') || { echo "Failed to parse JSON" >&2; exit 1; }

8. Logging Sensitive Data

# WRONG - may log secrets
echo "Processing: $input" >> /tmp/debug.log

# RIGHT - sanitize before logging
echo "Processing tool: $tool_name" >> /tmp/debug.log

When to Use Hooks

USE hooks for:

  • Security enforcement (block dangerous operations)
  • Code quality automation (format, lint on save)
  • Compliance and auditing (log all actions)
  • Environment setup (consistent configuration)
  • Workflow automation (notifications, integrations)
  • Input validation (prompt checking)
  • Task completion verification

DON'T use hooks for:

  • Adding new capabilities (use Skills)
  • Delegating complex work (use Agents)
  • User-invoked prompts (use Commands)
  • Simple one-off tasks (just ask Claude)

Files in This Skill

Templates (Progressive Complexity)

  • templates/basic-hook.md — Single event, inline command
  • templates/with-scripts.md — External shell scripts
  • templates/with-decisions.md — Permission control, input modification
  • templates/with-prompts.md — LLM-based evaluation
  • templates/production-hooks.md — Complete multi-event system

Examples (18 Complete Hooks)

  • examples/security-hooks.md — Protection, validation, auditing
  • examples/quality-hooks.md — Formatting, linting, testing
  • examples/workflow-hooks.md — Setup, context, notifications

Reference

  • reference/syntax-guide.md — Complete JSON schemas, all events
  • reference/best-practices.md — Security, design, team deployment
  • reference/troubleshooting.md — 10 common issues, testing methodology