Claude Code Hooks Advanced: Production Implementation Guide¶
Introduction¶
In the Claude Code Hooks Complete Guide, we covered the basic concepts, and in the AI Agent Automation Guide, we explored practical applications. This article dives deep into implementation techniques for stable operation in actual production environments and how to leverage hooks in team development.
Production-Ready Hook Implementation¶
1. Error Handling and Retry Mechanisms¶
#!/bin/bash
# production_hook.sh - Hook with error handling
set -euo pipefail # Exit immediately on error
IFS=$'\n\t' # Safe IFS setting
# Logging setup
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 process
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
# Async processing queue
QUEUE_DIR="$HOME/.claude-code/queue"
mkdir -p "$QUEUE_DIR"
# Add to queue (return 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 worker in background (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 process (return 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 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
# Process tasks
process_tasks() {
while true; do
# Get 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
# Delete processed task
rm -f "$processing_file"
fi
done
}
# Start worker
process_tasks
3. Security-Enhanced Hook¶
#!/bin/bash
# security_hook.sh - Hook with security checks
# Security settings
readonly ALLOWED_DIRS=(
"$HOME/projects"
"$HOME/workspace"
"/tmp/claude-code"
)
readonly FORBIDDEN_PATTERNS=(
"password"
"secret"
"api_key"
"private_key"
"credentials"
)
# Validate directory access
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
}
# Check for sensitive data
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 protection
sanitize_input() {
local input="$1"
# Escape dangerous characters
echo "$input" | sed 's/[;&|`$]//g'
}
# Main process
main() {
local tool_name=$(sanitize_input "${1:-}")
local file_path=$(sanitize_input "${2:-}")
# Validate path
validate_path "$file_path"
# Check for sensitive data
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 Applications¶
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 execution 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
# Print report
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. Check environment variables
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. Check configuration files
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. Check hook script 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. Check dependencies
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 on 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 meet your team's requirements.
Implementation Checklist¶
- Implement error handling and logging
- Performance optimization with async processing
- Introduce security checks
- Build team shared configuration
- Integrate with CI/CD pipeline
- Set up monitoring and alerts
In the next article, we'll explore 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.