spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / plugins / plugin-dev / skills / hook-development / references / patterns.md
440 lines · 9.0 KBPreviewCodeRaw

Common Hook Patterns

This reference provides common, proven patterns for implementing Claude Code hooks. Use these patterns as starting points for typical hook use cases.

Pattern 1: Security Validation

Block dangerous file writes using prompt-based hooks:

{
  "PreToolUse": [
    {
      "matcher": "Write|Edit",
      "hooks": [
        {
          "type": "prompt",
          "prompt": "File path: $TOOL_INPUT.file_path. Verify: 1) Not in /etc or system directories 2) Not .env or credentials 3) Path doesn't contain '..' traversal. Return 'approve' or 'deny'."
        }
      ]
    }
  ]
}

Use for: Preventing writes to sensitive files or system directories.

Pattern 2: Test Enforcement

Ensure tests run before stopping:

{
  "Stop": [
    {
      "matcher": "*",
      "hooks": [
        {
          "type": "prompt",
          "prompt": "Review transcript. If code was modified (Write/Edit tools used), verify tests were executed. If no tests were run, block with reason 'Tests must be run after code changes'."
        }
      ]
    }
  ]
}

Use for: Enforcing quality standards and preventing incomplete work.

Pattern 3: Context Loading

Load project-specific context at session start:

{
  "SessionStart": [
    {
      "matcher": "*",
      "hooks": [
        {
          "type": "command",
          "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh"
        }
      ]
    }
  ]
}

Example script (load-context.sh):

#!/bin/bash
cd "$CLAUDE_PROJECT_DIR" || exit 1

# Detect project type
if [ -f "package.json" ]; then
  echo "📦 Node.js project detected"
  echo "export PROJECT_TYPE=nodejs" >> "$CLAUDE_ENV_FILE"
elif [ -f "Cargo.toml" ]; then
  echo "🦀 Rust project detected"
  echo "export PROJECT_TYPE=rust" >> "$CLAUDE_ENV_FILE"
fi

Use for: Automatically detecting and configuring project-specific settings.

Pattern 4: Notification Logging

Log all notifications for audit or analysis:

{
  "Notification": [
    {
      "matcher": "*",
      "hooks": [
        {
          "type": "command",
          "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/log-notification.sh"
        }
      ]
    }
  ]
}

Use for: Tracking user notifications or integration with external logging systems.

Pattern 5: MCP Tool Monitoring

Monitor and validate MCP tool usage:

{
  "PreToolUse": [
    {
      "matcher": "mcp__.*__delete.*",
      "hooks": [
        {
          "type": "prompt",
          "prompt": "Deletion operation detected. Verify: Is this deletion intentional? Can it be undone? Are there backups? Return 'approve' only if safe."
        }
      ]
    }
  ]
}

Use for: Protecting against destructive MCP operations.

Pattern 6: Build Verification

Ensure project builds after code changes:

{
  "Stop": [
    {
      "matcher": "*",
      "hooks": [
        {
          "type": "prompt",
          "prompt": "Check if code was modified. If Write/Edit tools were used, verify the project was built (npm run build, cargo build, etc). If not built, block and request build."
        }
      ]
    }
  ]
}

Use for: Catching build errors before committing or stopping work.

Pattern 7: Permission Confirmation

Ask user before dangerous operations:

{
  "PreToolUse": [
    {
      "matcher": "Bash",
      "hooks": [
        {
          "type": "prompt",
          "prompt": "Command: $TOOL_INPUT.command. If command contains 'rm', 'delete', 'drop', or other destructive operations, return 'ask' to confirm with user. Otherwise 'approve'."
        }
      ]
    }
  ]
}

Use for: User confirmation on potentially destructive commands.

Pattern 8: Code Quality Checks

Run linters or formatters on file edits:

{
  "PostToolUse": [
    {
      "matcher": "Write|Edit",
      "hooks": [
        {
          "type": "command",
          "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/check-quality.sh"
        }
      ]
    }
  ]
}

Example script (check-quality.sh):

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

# Run linter if applicable
if [[ "$file_path" == *.js ]] || [[ "$file_path" == *.ts ]]; then
  npx eslint "$file_path" 2>&1 || true
fi

Use for: Automatic code quality enforcement.

Pattern Combinations

Combine multiple patterns for comprehensive protection:

{
  "PreToolUse": [
    {
      "matcher": "Write|Edit",
      "hooks": [
        {
          "type": "prompt",
          "prompt": "Validate file write safety"
        }
      ]
    },
    {
      "matcher": "Bash",
      "hooks": [
        {
          "type": "prompt",
          "prompt": "Validate bash command safety"
        }
      ]
    }
  ],
  "Stop": [
    {
      "matcher": "*",
      "hooks": [
        {
          "type": "prompt",
          "prompt": "Verify tests run and build succeeded"
        }
      ]
    }
  ],
  "SessionStart": [
    {
      "matcher": "*",
      "hooks": [
        {
          "type": "command",
          "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh"
        }
      ]
    }
  ]
}

This provides multi-layered protection and automation.

Pattern 9: Temporarily Active Hooks

Create hooks that only run when explicitly enabled via flag files:

#!/bin/bash
# Hook only active when flag file exists
FLAG_FILE="$CLAUDE_PROJECT_DIR/.enable-security-scan"

if [ ! -f "$FLAG_FILE" ]; then
  # Quick exit when disabled
  exit 0
fi

# Flag present, run validation
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')

# Run security scan
security-scanner "$file_path"

Activation:

# Enable the hook
touch .enable-security-scan

# Disable the hook
rm .enable-security-scan

Use for:

  • Temporary debugging hooks
  • Feature flags for development
  • Project-specific validation that's opt-in
  • Performance-intensive checks only when needed

Note: Must restart Claude Code after creating/removing flag files for hooks to recognize changes.

Pattern 10: Configuration-Driven Hooks

Use JSON configuration to control hook behavior:

#!/bin/bash
CONFIG_FILE="$CLAUDE_PROJECT_DIR/.claude/my-plugin.local.json"

# Read configuration
if [ -f "$CONFIG_FILE" ]; then
  strict_mode=$(jq -r '.strictMode // false' "$CONFIG_FILE")
  max_file_size=$(jq -r '.maxFileSize // 1000000' "$CONFIG_FILE")
else
  # Defaults
  strict_mode=false
  max_file_size=1000000
fi

# Skip if not in strict mode
if [ "$strict_mode" != "true" ]; then
  exit 0
fi

# Apply configured limits
input=$(cat)
file_size=$(echo "$input" | jq -r '.tool_input.content | length')

if [ "$file_size" -gt "$max_file_size" ]; then
  echo '{"decision": "deny", "reason": "File exceeds configured size limit"}' >&2
  exit 2
fi

Configuration file (.claude/my-plugin.local.json):

{
  "strictMode": true,
  "maxFileSize": 500000,
  "allowedPaths": ["/tmp", "/home/user/projects"]
}

Use for:

  • User-configurable hook behavior
  • Per-project settings
  • Team-specific rules
  • Dynamic validation criteria

Pattern 11: Teleport Workflow Automation

Automate setup when teleporting sessions between web and CLI:

Pre-teleport hook (prepare for transfer):

{
  "PreTeleport": [
    {
      "matcher": "*",
      "hooks": [
        {
          "type": "command",
          "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/pre-teleport.sh"
        }
      ]
    }
  ]
}

pre-teleport.sh:

#!/bin/bash
cd "$CLAUDE_PROJECT_DIR" || exit 0

# Stash uncommitted changes before teleporting
if [ -d ".git" ] && [ -n "$(git status --porcelain)" ]; then
  echo "📦 Stashing uncommitted changes..."
  git stash push -m "pre-teleport-$(date +%s)"
fi

# Save current state for restoration
echo "$(git branch --show-current)" > .claude/.teleport-state

Post-teleport hook (set up after transfer):

{
  "PostTeleport": [
    {
      "matcher": "*",
      "hooks": [
        {
          "type": "command",
          "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/post-teleport.sh"
        }
      ]
    }
  ]
}

post-teleport.sh:

#!/bin/bash
cd "$CLAUDE_PROJECT_DIR" || exit 0

# Pull latest changes
if [ -d ".git" ]; then
  echo "🔄 Pulling latest changes..."
  git pull origin "$(git branch --show-current)" 2>/dev/null || true

  # Restore stashed changes if any
  if git stash list | grep -q "pre-teleport"; then
    echo "📦 Restoring stashed changes..."
    git stash pop
  fi
fi

# Install dependencies
if [ -f "package.json" ]; then
  echo "📦 Installing dependencies..."
  npm install --silent
fi

# Start dev server
if [ -f "package.json" ] && grep -q '"dev:staging"' package.json; then
  echo "🚀 Starting staging dev server..."
  npm run dev:staging &
elif [ -f "package.json" ] && grep -q '"dev"' package.json; then
  echo "🚀 Starting dev server..."
  npm run dev &
fi

echo "✅ Teleport complete! Environment ready."

Use for:

  • Seamless web-to-CLI workflow transitions
  • Automatic dev server startup after teleporting
  • Preserving uncommitted work across teleports
  • Environment setup automation