Skip to content

Claude Code Complete Guide

Claude Code Hooks Advanced: Production Implementation Guide

Introduction

The Claude Code Hooks Complete Guide covered the fundamental concepts, and the AI Agent Automation Guide explored practical applications. This article delves into implementation techniques for stable operation in production environments and how to leverage hooks in team development settings.

Production-Ready Hook Implementation

1. Error Handling and Retry Mechanism

#!/bin/bash
# production_hook.sh - Hook with error handling

set -euo pipefail  # Exit immediately on error
IFS=$'\n\t'        # Safe IFS setting

# Logging configuration
LOG_DIR="$HOME/.claude-code/logs"
LOG_FILE="$LOG_DIR/hook_$(date +%Y%m%d_%H%M%S).log"
mkdir -p "$LOG_DIR"

# Log function
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# Error handler
error_handler() {
    local line_no=$1
    local error_code=$2
    log "ERROR: Line $line_no exited with code $error_code"

    # Slack notification (optional)
    if [ -n "${SLACK_WEBHOOK_URL:-}" ]; then
        curl -X POST "$SLACK_WEBHOOK_URL" \
            -H 'Content-Type: application/json' \
            -d "{\"text\":\"Hook Error: Line $line_no, Code $error_code\"}" \
            2>/dev/null || true
    fi

    exit $error_code
}

trap 'error_handler ${LINENO} $?' ERR

# Retry functionality
retry_with_backoff() {
    local max_attempts=3
    local timeout=1
    local attempt=1
    local exitCode=0

    while [ $attempt -le $max_attempts ]; do
        if "$@"; then
            return 0
        else
            exitCode=$?
        fi

        log "Attempt $attempt failed. Retrying in $timeout seconds..."
        sleep $timeout
        attempt=$(( attempt + 1 ))
        timeout=$(( timeout * 2 ))
    done

    log "Command failed after $max_attempts attempts"
    return $exitCode
}

# Main processing
main() {
    local tool_name="${1:-unknown}"
    local file_path="${2:-}"

    log "Starting hook for tool: $tool_name, file: $file_path"

    # Process based on file type
    case "$file_path" in
        *.py)
            log "Processing Python file"
            retry_with_backoff python -m py_compile "$file_path"
            retry_with_backoff ruff check "$file_path" --fix
            retry_with_backoff mypy "$file_path" --ignore-missing-imports
            ;;
        *.js|*.ts|*.jsx|*.tsx)
            log "Processing JavaScript/TypeScript file"
            retry_with_backoff npx eslint "$file_path" --fix
            retry_with_backoff npx prettier --write "$file_path"
            ;;
        *.md)
            log "Processing Markdown file"
            retry_with_backoff npx markdownlint "$file_path" --fix
            ;;
        *)
            log "No specific handler for file type"
            ;;
    esac

    log "Hook completed successfully"
}

# Execute script
main "$@"

2. Performance-Optimized Hook

{
  "hooks": {
    "postToolUse": {
      "write": {
        "command": "~/.claude-code/hooks/optimized_hook.sh {{tool_name}} {{file_path}}"
      }
    }
  }
}
#!/bin/bash
# optimized_hook.sh - Performance-optimized version

# Asynchronous task queue
QUEUE_DIR="$HOME/.claude-code/queue"
mkdir -p "$QUEUE_DIR"

# Add to queue (returns immediately)
queue_task() {
    local task_id=$(date +%s%N)
    local task_file="$QUEUE_DIR/$task_id.task"

    cat > "$task_file" << EOF
{
  "tool": "$1",
  "file": "$2",
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
EOF

    # Start background worker (ignore if already running)
    if ! pgrep -f "claude_hook_worker" > /dev/null; then
        nohup ~/.claude-code/hooks/worker.sh > /dev/null 2>&1 &
    fi

    echo "Task queued: $task_id"
}

# Main processing (returns immediately)
queue_task "$1" "$2"
exit 0
#!/bin/bash
# worker.sh - Background worker

QUEUE_DIR="$HOME/.claude-code/queue"
PROCESSING_DIR="$HOME/.claude-code/processing"
mkdir -p "$PROCESSING_DIR"

# Check for existing worker
if [ -f "$PROCESSING_DIR/worker.pid" ]; then
    old_pid=$(cat "$PROCESSING_DIR/worker.pid")
    if kill -0 "$old_pid" 2>/dev/null; then
        echo "Worker already running (PID: $old_pid)"
        exit 0
    fi
fi

# Create PID file
echo $$ > "$PROCESSING_DIR/worker.pid"

# Cleanup handler
cleanup() {
    rm -f "$PROCESSING_DIR/worker.pid"
    exit 0
}
trap cleanup EXIT INT TERM

# Task processing
process_tasks() {
    while true; do
        # Get the oldest task
        task_file=$(ls -1t "$QUEUE_DIR"/*.task 2>/dev/null | tail -1)

        if [ -z "$task_file" ]; then
            # Wait 5 seconds if no tasks
            sleep 5
            continue
        fi

        # Move task to processing directory
        processing_file="$PROCESSING_DIR/$(basename "$task_file")"
        mv "$task_file" "$processing_file" 2>/dev/null || continue

        # Execute task
        if [ -f "$processing_file" ]; then
            # Parse JSON (if jq is installed)
            if command -v jq &> /dev/null; then
                tool=$(jq -r '.tool' "$processing_file")
                file=$(jq -r '.file' "$processing_file")

                # Execute actual processing
                ~/.claude-code/hooks/process_file.sh "$tool" "$file"
            fi

            # Remove completed task
            rm -f "$processing_file"
        fi
    done
}

# Start worker
process_tasks

3. Security-Hardened Hook

#!/bin/bash
# security_hook.sh - Hook with security checks

# Security configuration
readonly ALLOWED_DIRS=(
    "$HOME/projects"
    "$HOME/workspace"
    "/tmp/claude-code"
)

readonly FORBIDDEN_PATTERNS=(
    "password"
    "secret"
    "api_key"
    "private_key"
    "credentials"
)

# Directory access validation
validate_path() {
    local file_path="$1"
    local real_path=$(realpath "$file_path" 2>/dev/null)

    for allowed_dir in "${ALLOWED_DIRS[@]}"; do
        if [[ "$real_path" == "$allowed_dir"* ]]; then
            return 0
        fi
    done

    echo "ERROR: Access denied to path: $file_path" >&2
    exit 1
}

# Sensitive data check
check_sensitive_data() {
    local file_path="$1"

    for pattern in "${FORBIDDEN_PATTERNS[@]}"; do
        if grep -i "$pattern" "$file_path" > /dev/null 2>&1; then
            echo "WARNING: Possible sensitive data detected (pattern: $pattern)"

            # Confirmation prompt (if interactive)
            if [ -t 0 ]; then
                read -p "Continue anyway? (y/N) " -n 1 -r
                echo
                if [[ ! $REPLY =~ ^[Yy]$ ]]; then
                    exit 1
                fi
            fi
        fi
    done
}

# Command injection prevention
sanitize_input() {
    local input="$1"
    # Escape dangerous characters
    echo "$input" | sed 's/[;&|`$]//g'
}

# Main processing
main() {
    local tool_name=$(sanitize_input "${1:-}")
    local file_path=$(sanitize_input "${2:-}")

    # Path validation
    validate_path "$file_path"

    # Sensitive data check
    if [[ "$tool_name" == "write" ]] || [[ "$tool_name" == "edit" ]]; then
        check_sensitive_data "$file_path"
    fi

    # Actual processing
    echo "Security checks passed for: $file_path"
}

main "$@"

Team Development Usage

1. Shared Hook Configuration System

#!/bin/bash
# setup_team_hooks.sh - Team shared hook setup

TEAM_REPO="https://github.com/your-team/claude-hooks.git"
HOOKS_DIR="$HOME/.claude-code/team-hooks"

# Clone/update team hook repository
setup_team_hooks() {
    if [ -d "$HOOKS_DIR/.git" ]; then
        echo "Updating team hooks..."
        cd "$HOOKS_DIR" && git pull
    else
        echo "Cloning team hooks..."
        git clone "$TEAM_REPO" "$HOOKS_DIR"
    fi

    # Grant execute permissions
    chmod +x "$HOOKS_DIR"/*.sh

    # Create symbolic link
    ln -sf "$HOOKS_DIR/hooks.json" "$HOME/.config/claude-code/hooks.json"
}

# Apply environment-specific configuration
apply_environment_config() {
    local env="${CLAUDE_ENV:-development}"
    local env_config="$HOOKS_DIR/config/$env.json"

    if [ -f "$env_config" ]; then
        echo "Applying $env environment configuration..."
        jq -s '.[0] * .[1]' \
            "$HOOKS_DIR/hooks.json" \
            "$env_config" \
            > "$HOME/.config/claude-code/hooks.json"
    fi
}

# Execute
setup_team_hooks
apply_environment_config

echo "Team hooks setup completed!"

2. Hook Execution Log Aggregation

#!/usr/bin/env python3
# aggregate_logs.py - Hook log aggregation script

import json
import os
import glob
from datetime import datetime
from collections import defaultdict
import pandas as pd

class HookLogAggregator:
    def __init__(self, log_dir="~/.claude-code/logs"):
        self.log_dir = os.path.expanduser(log_dir)

    def parse_logs(self):
        """Parse log files"""
        logs = []

        for log_file in glob.glob(f"{self.log_dir}/*.log"):
            with open(log_file, 'r') as f:
                for line in f:
                    if line.strip():
                        logs.append(self.parse_log_line(line, log_file))

        return logs

    def parse_log_line(self, line, filename):
        """Parse log line"""
        # [2025-01-18 12:34:56] Starting hook for tool: write, file: test.py
        parts = line.split(']', 1)
        if len(parts) == 2:
            timestamp_str = parts[0].strip('[')
            message = parts[1].strip()

            return {
                'timestamp': datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S'),
                'message': message,
                'filename': os.path.basename(filename),
                'level': self.detect_log_level(message)
            }
        return None

    def detect_log_level(self, message):
        """Detect log level"""
        if 'ERROR' in message:
            return 'ERROR'
        elif 'WARNING' in message:
            return 'WARNING'
        elif 'INFO' in message:
            return 'INFO'
        return 'DEBUG'

    def generate_report(self):
        """Generate report"""
        logs = [log for log in self.parse_logs() if log]

        if not logs:
            print("No logs found")
            return

        df = pd.DataFrame(logs)

        # Error statistics
        error_stats = df[df['level'] == 'ERROR'].groupby(
            df['timestamp'].dt.date
        ).size()

        # Tool usage statistics
        tool_stats = defaultdict(int)
        for message in df['message']:
            if 'tool:' in message:
                tool = message.split('tool:')[1].split(',')[0].strip()
                tool_stats[tool] += 1

        # Report output
        print("=== Claude Code Hook Usage Report ===")
        print(f"\nTotal log entries: {len(df)}")
        print(f"Date range: {df['timestamp'].min()} - {df['timestamp'].max()}")

        print("\n--- Error Statistics ---")
        for date, count in error_stats.items():
            print(f"{date}: {count} errors")

        print("\n--- Tool Usage ---")
        for tool, count in sorted(tool_stats.items(), key=lambda x: x[1], reverse=True):
            print(f"{tool}: {count} times")

        # Save detailed report to file
        report_file = f"hook_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
        with open(report_file, 'w') as f:
            json.dump({
                'summary': {
                    'total_entries': len(df),
                    'date_range': {
                        'start': str(df['timestamp'].min()),
                        'end': str(df['timestamp'].max())
                    },
                    'error_count': len(df[df['level'] == 'ERROR']),
                    'warning_count': len(df[df['level'] == 'WARNING'])
                },
                'tool_usage': dict(tool_stats),
                'daily_errors': {str(k): int(v) for k, v in error_stats.items()}
            }, f, indent=2)

        print(f"\nDetailed report saved to: {report_file}")

if __name__ == "__main__":
    aggregator = HookLogAggregator()
    aggregator.generate_report()

3. CI/CD Integration

# .github/workflows/claude-hooks-test.yml
name: Claude Code Hooks Test

on:
  pull_request:
    paths:
      - '.claude-code/hooks/**'
      - 'hooks.json'

jobs:
  test-hooks:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'

    - name: Install dependencies
      run: |
        npm install -g eslint prettier markdownlint-cli
        pip install ruff mypy

    - name: Test Hook Scripts
      run: |
        # Syntax check for hook scripts
        for hook in .claude-code/hooks/*.sh; do
          echo "Testing $hook..."
          bash -n "$hook"
        done

    - name: Test Hook Configuration
      run: |
        # Validate hooks.json
        python -m json.tool hooks.json > /dev/null
        echo "Hook configuration is valid JSON"

    - name: Simulate Hook Execution
      run: |
        # Simulate hook execution with test file
        echo "print('test')" > test.py
        .claude-code/hooks/production_hook.sh write test.py || true

    - name: Check Hook Logs
      run: |
        if [ -d "$HOME/.claude-code/logs" ]; then
          echo "Hook logs:"
          cat $HOME/.claude-code/logs/*.log || true
        fi

Troubleshooting Guide

Issue 1: Hooks Not Executing

#!/bin/bash
# debug_hooks.sh - Hook diagnostic script

echo "=== Claude Code Hooks Diagnostic ==="

# 1. Environment variable check
echo -e "\n1. Environment Variables:"
echo "CLAUDE_CODE_HOOKS_ENABLED: ${CLAUDE_CODE_HOOKS_ENABLED:-not set}"
echo "CLAUDE_CODE_HOOKS_CONFIG: ${CLAUDE_CODE_HOOKS_CONFIG:-not set}"

# 2. Configuration file check
echo -e "\n2. Configuration Files:"
config_file="${CLAUDE_CODE_HOOKS_CONFIG:-$HOME/.config/claude-code/hooks.json}"
if [ -f "$config_file" ]; then
    echo "Config file exists: $config_file"
    echo "Config file valid: $(python -m json.tool "$config_file" > /dev/null 2>&1 && echo "Yes" || echo "No")"
else
    echo "Config file NOT FOUND: $config_file"
fi

# 3. Hook script execution permissions
echo -e "\n3. Hook Scripts Permissions:"
hook_dir="$HOME/.claude-code/hooks"
if [ -d "$hook_dir" ]; then
    for script in "$hook_dir"/*.sh; do
        if [ -f "$script" ]; then
            echo "$(basename "$script"): $([ -x "$script" ] && echo "executable" || echo "NOT executable")"
        fi
    done
else
    echo "Hook directory NOT FOUND: $hook_dir"
fi

# 4. Dependency check
echo -e "\n4. Dependencies:"
commands=("bash" "jq" "curl" "git" "npm" "python3")
for cmd in "${commands[@]}"; do
    echo "$cmd: $(command -v $cmd > /dev/null && echo "installed" || echo "NOT installed")"
done

# 5. Test execution
echo -e "\n5. Test Hook Execution:"
test_hook="$hook_dir/test_hook.sh"
cat > "$test_hook" << 'EOF'
#!/bin/bash
echo "Test hook executed successfully!"
exit 0
EOF
chmod +x "$test_hook"

if $test_hook > /dev/null 2>&1; then
    echo "Test hook execution: SUCCESS"
else
    echo "Test hook execution: FAILED"
fi

rm -f "$test_hook"

Issue 2: Performance Problems

# performance_monitor.sh - Performance monitoring

#!/bin/bash
METRICS_FILE="$HOME/.claude-code/metrics.log"

# Measure hook execution time
measure_hook_performance() {
    local hook_name="$1"
    local start_time=$(date +%s.%N)

    # Execute hook
    shift
    "$@"
    local exit_code=$?

    local end_time=$(date +%s.%N)
    local duration=$(echo "$end_time - $start_time" | bc)

    # Record metrics
    echo "$(date -u +%Y-%m-%dT%H:%M:%SZ),${hook_name},${duration},${exit_code}" >> "$METRICS_FILE"

    # Warn about slow execution
    if (( $(echo "$duration > 5" | bc -l) )); then
        echo "WARNING: Hook '$hook_name' took ${duration}s to execute"
    fi

    return $exit_code
}

# Usage example
measure_hook_performance "code_quality_check" ~/.claude-code/hooks/lint.sh "$@"

Summary

To leverage Claude Code Hooks in production environments, error handling, performance optimization, and security measures are essential. Use the implementation patterns introduced in this article as a reference and customize them to match your team's requirements.

Implementation Checklist

  • Implement error handling and logging
  • Optimize performance with async processing
  • Introduce security checks
  • Build shared team configuration
  • Integrate with CI/CD pipeline
  • Set up monitoring and alerts

Next time, we'll cover advanced workflow automation using Claude Code Hooks.


This article was published on January 18, 2025. Implementation examples are continuously updated.