Claude Code Hooks Tutorials & Examples

Progressive tutorials from beginner to advanced with practical examples.

Workshop Overview

  • Duration: 5 hours full workshop (2.5 hours express)
  • Prerequisites: Basic command line, Claude Code familiarity
  • Goal: Master hooks from basics to advanced patterns

Getting Started

Environment Setup

# Create practice project
mkdir claude-hooks-workshop
cd claude-hooks-workshop
mkdir .claude
 
# Create initial configuration
echo '{"hooks": {}}' > .claude/settings.json
 
# Open Claude Code and verify
claude-code
# Run: /hooks

Required Tools

  • jq - JSON processor
  • curl - HTTP client
  • git - Version control
  • python3 - For advanced examples (optional)

Beginner Examples (45 min)

1. Your First Hook - Basic Logging

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": ".*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Claude used a tool!' >> claude-log.txt"
          }
        ]
      }
    ]
  }
}

2. Desktop Notifications

macOS:

{
  "hooks": {
    "Stop": [{
      "hooks": [{
        "command": "osascript -e 'display notification \"Task completed!\" with title \"Claude Code\"'"
      }]
    }]
  }
}

Linux:

{
  "hooks": {
    "Stop": [{
      "hooks": [{
        "command": "notify-send 'Claude Code' 'Task completed!'"
      }]
    }]
  }
}

3. File Backup Before Edits

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|MultiEdit",
        "hooks": [{
          "command": "for file in $CLAUDE_FILE_PATHS; do cp \"$file\" \"$file.backup\"; done"
        }]
      }
    ]
  }
}

4. Activity Tracking

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [{
          "command": "echo \"$(date): Modified $CLAUDE_FILE_PATHS\" >> ~/.claude/activity.log"
        }]
      }
    ]
  }
}

5. Simple Validation

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [{
          "command": "if [[ \"$CLAUDE_FILE_PATHS\" == *\".env\"* ]]; then echo 'Cannot modify .env files!' >&2; exit 2; fi"
        }]
      }
    ]
  }
}

Exercise: Timestamped Backups ⭐⭐

Create backups with timestamps instead of .backup.

Solution
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|MultiEdit",
        "hooks": [{
          "command": "for file in $CLAUDE_FILE_PATHS; do cp \"$file\" \"$file.backup.$(date +%Y%m%d_%H%M%S)\"; done"
        }]
      }
    ]
  }
}

Intermediate Examples (60 min)

1. Multi-Language Code Formatter

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [{
          "command": "for file in $CLAUDE_FILE_PATHS; do case \"$file\" in *.js|*.jsx|*.ts|*.tsx) prettier --write \"$file\" ;; *.py) black \"$file\" ;; *.go) gofmt -w \"$file\" ;; *.rs) rustfmt \"$file\" ;; esac; done"
        }]
      }
    ]
  }
}

2. Type Checking & Linting

#!/bin/bash
# Save as: .claude/hooks/type_check.sh
 
for file in $CLAUDE_FILE_PATHS; do
  if [[ "$file" =~ \.(ts|tsx)$ ]]; then
    echo "Type checking $file..."
    npx tsc --noEmit --skipLibCheck "$file" || echo "⚠️ Type errors in $file"
    echo "Linting $file..."
    eslint --fix "$file" || echo "⚠️ Lint errors in $file"
  fi
done

3. Test Runner for Modified Files

#!/usr/bin/env python3
# Save as: .claude/hooks/smart_test.py
 
import json
import sys
import os
import subprocess
from pathlib import Path
 
def find_test_file(source_file):
    path = Path(source_file)
    test_patterns = [
        path.parent / f"test_{path.stem}.py",
        path.parent / f"{path.stem}_test.py",
        path.parent / "tests" / f"test_{path.stem}.py"
    ]
    
    for test_path in test_patterns:
        if test_path.exists():
            return str(test_path)
    return None
 
data = json.load(sys.stdin)
files = os.environ.get('CLAUDE_FILE_PATHS', '').split()
 
for file in files:
    if 'test' not in file:
        test_file = find_test_file(file)
        if test_file:
            print(f"Running tests for {file}...")
            subprocess.run(['pytest', test_file, '-v'])

4. Git Pre-commit Integration

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{
          "command": "if echo \"$CLAUDE_TOOL_INPUT\" | jq -r '.command' | grep -q 'git commit'; then prettier --write '**/*.{js,ts,json}' 2>/dev/null || true; npm test || exit 2; fi"
        }]
      }
    ]
  }
}

Exercise: Quality Gate ⭐⭐⭐

Create a comprehensive quality check that formats, lints, and tests code.

Solution
#!/bin/bash
# Save as: .claude/hooks/quality_gate.sh
 
ERRORS=0
 
# Format
for file in $CLAUDE_FILE_PATHS; do
  case "$file" in
    *.py) black "$file" 2>/dev/null || ((ERRORS++)) ;;
    *.js|*.ts) prettier --write "$file" 2>/dev/null || ((ERRORS++)) ;;
  esac
done
 
# Lint
for file in $CLAUDE_FILE_PATHS; do
  case "$file" in
    *.py) pylint "$file" --exit-zero ;;
    *.js|*.ts) eslint "$file" --fix 2>/dev/null ;;
  esac
done
 
# Test
npm test 2>/dev/null || ((ERRORS++))
 
if [ $ERRORS -gt 0 ]; then
  echo "Quality gate failed with $ERRORS errors"
  exit 2
fi

Advanced Examples (90 min)

1. CI/CD Pipeline Integration

#!/bin/bash
# Automated PR creation with checks
 
if git diff --quiet && git diff --staged --quiet; then
  echo 'No changes'
  exit 0
fi
 
BRANCH="claude-$(date +%s)"
git checkout -b "$BRANCH"
git add -A
git commit -m "Claude Code: Automated improvements"
git push -u origin "$BRANCH"
 
gh pr create \
  --title "Claude Code: Automated improvements" \
  --body "Automated changes by Claude Code" \
  --label "claude-code"

2. Multi-Service Orchestration

#!/usr/bin/env python3
import json
import sys
import subprocess
from pathlib import Path
 
def detect_service(file_path):
    """Detect which service a file belongs to"""
    path = Path(file_path)
    service_map = {
        'frontend': ['src/components', 'src/pages'],
        'api': ['api/', 'server/'],
        'database': ['migrations/', 'models/']
    }
    
    for service, patterns in service_map.items():
        for pattern in patterns:
            if pattern in str(path):
                return service
    return 'unknown'
 
data = json.load(sys.stdin)
files = os.environ.get('CLAUDE_FILE_PATHS', '').split()
affected_services = set()
 
for file in files:
    service = detect_service(file)
    affected_services.add(service)
 
# Handle service-specific tasks
if 'frontend' in affected_services:
    subprocess.run(['npm', 'run', 'build'])
    
if 'api' in affected_services:
    subprocess.run(['npm', 'test', '--', '--testPathPattern=api'])

3. Security Scanning

#!/usr/bin/env python3
import json
import sys
import subprocess
 
data = json.load(sys.stdin)
file_path = data.get('tool_input', {}).get('file_path', '')
 
# Security checks
if file_path.endswith('.py'):
    result = subprocess.run(
        ['bandit', '-f', 'json', file_path],
        capture_output=True,
        text=True
    )
    
    if result.returncode != 0:
        issues = json.loads(result.stdout)
        if issues.get('results'):
            print(json.dumps({
                'continue': False,
                'stopReason': f"Security issues found: {len(issues['results'])}"
            }))
            sys.exit(2)

Workshop Exercises

Beginner Challenges

  1. Time-stamped Notifications

    • Modify notifications to include current time
  2. Daily Summary ⭐⭐

    • Create a daily activity summary
  3. File Size Check ⭐⭐

    • Block edits to files over 1MB

Intermediate Challenges

  1. JSON/YAML Formatter ⭐⭐⭐

    • Add JSON and YAML to the formatter
  2. Smart Test Runner ⭐⭐⭐

    • Run only tests for modified source files
  3. Coverage Check ⭐⭐⭐⭐

    • Ensure code coverage stays above 80%

Advanced Challenges

  1. Auto-merge CI ⭐⭐⭐⭐

    • Create PRs that auto-merge if tests pass
  2. Blue-Green Deploy ⭐⭐⭐⭐⭐

    • Implement zero-downtime deployments
  3. Observability System ⭐⭐⭐⭐⭐

    • Build complete monitoring with metrics

Tips for Success

  1. Start Simple: Test basic hooks before complex ones
  2. Use Logging: Debug with comprehensive logs
  3. Handle Errors: Always include error handling
  4. Test Manually: Run commands outside hooks first
  5. Document: Explain what your hooks do

Quick Reference Card

Hook Events

  • PreToolUse - Before tools (can block)
  • PostToolUse - After tools
  • Stop - When Claude finishes
  • Notification - On notifications
  • SubagentStop - Subagent completion

Exit Codes

  • 0 - Success, continue
  • 2 - Block operation
  • Other - Warning, continue

Environment Variables

  • $CLAUDE_TOOL_NAME
  • $CLAUDE_FILE_PATHS
  • $CLAUDE_TOOL_INPUT
  • $CLAUDE_NOTIFICATION

Next Steps