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.
Related Articles¶
- Claude Code Hooks Complete Guide: A New Era of AI Agent Automation
- Claude Code Official Documentation
- Production-Ready Hook Implementation Examples
This article was published on January 18, 2025. Implementation examples are continuously updated.