claude-code hooks exercises solutions examples hands-on

Workshop Exercise Solutions

Complete solutions for all workshop exercises with detailed explanations.

Beginner Exercise Solutions

Exercise 1: Time-stamped Notifications ⭐

Problem: Modify the notification to show the current time.

Solution:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Task completed at '$(date +\"%H:%M:%S\")'\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

Explanation: We use command substitution $(date +"%H:%M:%S") to insert the current time into the notification message.

Exercise 2: Timestamped Backups ⭐⭐

Problem: Create timestamped backups instead of simple .backup files.

Solution:

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

Explanation: The date +%Y%m%d_%H%M%S format creates a sortable timestamp like 20250119_143052.

Exercise 3: Daily Activity Summary ⭐⭐

Problem: Create a daily summary of Claude’s activities.

Solution:

#!/bin/bash
# Save as: .claude/hooks/daily_summary.sh
 
LOG_FILE="$HOME/.claude/activity.log"
SUMMARY_FILE="$HOME/.claude/daily_summary_$(date +%Y%m%d).txt"
 
# Count activities by type
{
    echo "=== Claude Code Daily Summary - $(date) ==="
    echo ""
    echo "Files Modified:"
    grep "$(date +%Y-%m-%d)" "$LOG_FILE" | grep -o "Modified .*" | sort | uniq -c
    echo ""
    echo "Total Operations: $(grep -c "$(date +%Y-%m-%d)" "$LOG_FILE")"
} > "$SUMMARY_FILE"
 
echo "Daily summary saved to: $SUMMARY_FILE"

Exercise 4: Comprehensive File Tracking ⭐⭐⭐

Problem: Create a hook that logs file modifications to CSV, includes timestamp/filename/tool, and prevents modifications to files larger than 1MB.

Solution:

#!/bin/bash
# Save as: .claude/hooks/file_tracker.sh
 
CSV_FILE="$HOME/.claude/file_modifications.csv"
JSON_INPUT=$(cat)
 
# Initialize CSV if it doesn't exist
if [ ! -f "$CSV_FILE" ]; then
    echo "timestamp,filename,tool,size_bytes,action" > "$CSV_FILE"
fi
 
# Extract data
TOOL_NAME=$(echo "$JSON_INPUT" | jq -r '.tool_name')
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
 
# Process each file
for file in $CLAUDE_FILE_PATHS; do
    if [ -f "$file" ]; then
        SIZE=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)
        
        # Check size limit
        if [ "$SIZE" -gt 1048576 ]; then
            echo "{\"continue\": false, \"stopReason\": \"File $file exceeds 1MB limit ($SIZE bytes)\"}"
            exit 2
        fi
        
        # Log to CSV
        echo "$TIMESTAMP,\"$file\",$TOOL_NAME,$SIZE,modified" >> "$CSV_FILE"
    fi
done
 
exit 0

Intermediate Exercise Solutions

Exercise 1: JSON/YAML Formatting ⭐⭐

Problem: Add support for JSON and YAML formatting to the multi-language formatter.

Solution:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "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\" ;; *.json) jq --indent 2 . \"$file\" > \"$file.tmp\" && mv \"$file.tmp\" \"$file\" ;; *.yml|*.yaml) prettier --write \"$file\" || yamllint -f auto \"$file\" ;; esac; done"
          }
        ]
      }
    ]
  }
}

Exercise 2: Test Runner for Source Files ⭐⭐⭐

Problem: Create a hook that runs only the tests related to modified source files.

Solution:

#!/usr/bin/env python3
# Save as: .claude/hooks/smart_test_runner.py
 
import json
import sys
import os
import subprocess
from pathlib import Path
 
def find_test_file(source_file):
    """Find corresponding test file for a source file"""
    path = Path(source_file)
    stem = path.stem
    
    # Common test file patterns
    test_patterns = [
        path.parent / f"test_{stem}.py",
        path.parent / f"{stem}_test.py",
        path.parent / "tests" / f"test_{stem}.py",
        path.parent.parent / "tests" / path.parent.name / f"test_{stem}.py",
        path.with_suffix(".test" + path.suffix),
        path.parent / "__tests__" / f"{stem}.test{path.suffix}"
    ]
    
    for test_path in test_patterns:
        if test_path.exists():
            return str(test_path)
    
    return None
 
def run_tests(test_files):
    """Run tests based on file type"""
    results = []
    
    for test_file in test_files:
        if test_file.endswith('.py'):
            result = subprocess.run(['pytest', test_file, '-v'], 
                                  capture_output=True, text=True)
        elif test_file.endswith(('.js', '.ts')):
            result = subprocess.run(['npm', 'test', '--', test_file], 
                                  capture_output=True, text=True)
        else:
            continue
            
        results.append({
            'file': test_file,
            'success': result.returncode == 0,
            'output': result.stdout if result.returncode == 0 else result.stderr
        })
    
    return results
 
def main():
    data = json.load(sys.stdin)
    
    # Only run for source file modifications
    if data.get('tool_name') not in ['Edit', 'Write', 'MultiEdit']:
        return 0
    
    files = os.environ.get('CLAUDE_FILE_PATHS', '').split()
    test_files = []
    
    for file in files:
        # Skip if already a test file
        if 'test' in file or 'spec' in file:
            continue
            
        test_file = find_test_file(file)
        if test_file:
            test_files.append(test_file)
    
    if test_files:
        print(f"Running tests for: {', '.join(test_files)}")
        results = run_tests(test_files)
        
        failed = [r for r in results if not r['success']]
        if failed:
            print(json.dumps({
                'continue': False,
                'stopReason': f"{len(failed)} tests failed"
            }))
            return 2
    
    return 0
 
if __name__ == '__main__':
    sys.exit(main())

Exercise 3: Quality Gate ⭐⭐⭐⭐

Problem: Create a comprehensive quality gate hook.

Solution:

#!/bin/bash
# Save as: .claude/hooks/quality_gate.sh
 
ERRORS=0
WARNINGS=0
 
log_result() {
    local status=$1
    local message=$2
    if [ "$status" -eq 0 ]; then
        echo "✅ $message"
    else
        echo "❌ $message"
        ((ERRORS++))
    fi
}
 
# 1. Format code
format_code() {
    echo "=== Code Formatting ==="
    for file in $CLAUDE_FILE_PATHS; do
        case "$file" in
            *.py)
                black "$file" 2>/dev/null
                log_result $? "Formatted Python: $file"
                ;;
            *.js|*.ts|*.jsx|*.tsx)
                prettier --write "$file" 2>/dev/null
                log_result $? "Formatted JS/TS: $file"
                ;;
            *.go)
                gofmt -w "$file" 2>/dev/null
                log_result $? "Formatted Go: $file"
                ;;
        esac
    done
}
 
# 2. Run linters
run_linters() {
    echo -e "\n=== Linting ==="
    for file in $CLAUDE_FILE_PATHS; do
        case "$file" in
            *.py)
                pylint "$file" --exit-zero
                log_result $? "Linted Python: $file"
                ;;
            *.js|*.ts)
                eslint "$file" --fix 2>/dev/null
                log_result $? "Linted JS/TS: $file"
                ;;
        esac
    done
}
 
# 3. Run tests
run_tests() {
    echo -e "\n=== Testing ==="
    if [ -f "package.json" ] && grep -q "test" package.json; then
        npm test
        log_result $? "JavaScript tests"
    fi
    
    if [ -f "setup.py" ] || [ -f "pyproject.toml" ]; then
        pytest --tb=short
        log_result $? "Python tests"
    fi
}
 
# 4. Check coverage
check_coverage() {
    echo -e "\n=== Code Coverage ==="
    if [ -f "package.json" ] && grep -q "coverage" package.json; then
        COVERAGE=$(npm run coverage --silent | grep -oE '[0-9]+%' | head -1 | tr -d '%')
        if [ "$COVERAGE" -lt 80 ]; then
            echo "⚠️  Coverage is ${COVERAGE}% (target: 80%)"
            ((WARNINGS++))
        else
            echo "✅ Coverage is ${COVERAGE}%"
        fi
    fi
}
 
# 5. Validate commit message
validate_commit() {
    echo -e "\n=== Commit Validation ==="
    # This would run if we're about to commit
    if [ -n "$CLAUDE_COMMIT_MSG" ]; then
        if [[ "$CLAUDE_COMMIT_MSG" =~ ^(feat|fix|docs|style|refactor|test|chore):.+ ]]; then
            echo "✅ Commit message follows convention"
        else
            echo "❌ Commit message must follow format: type: description"
            ((ERRORS++))
        fi
    fi
}
 
# Execute all checks
echo "🚀 Running Quality Gate..."
echo "========================="
 
format_code
run_linters
run_tests
check_coverage
validate_commit
 
echo -e "\n========================="
echo "📊 Quality Gate Summary:"
echo "   Errors: $ERRORS"
echo "   Warnings: $WARNINGS"
 
if [ $ERRORS -gt 0 ]; then
    echo -e "\n❌ Quality gate FAILED"
    exit 2
else
    echo -e "\n✅ Quality gate PASSED"
    exit 0
fi

Advanced Exercise Solutions

Exercise 1: CI/CD with Auto-merge ⭐⭐⭐⭐

Problem: Extend the CI/CD integration to include automatic PR checks and merge if all tests pass.

Solution:

#!/bin/bash
# Save as: .claude/hooks/auto_merge_ci.sh
 
# Configuration
BRANCH_PREFIX="claude-auto"
REQUIRED_CHECKS=("test" "lint" "security")
 
create_and_push_branch() {
    BRANCH="${BRANCH_PREFIX}-$(date +%s)"
    git checkout -b "$BRANCH"
    git add -A
    git commit -m "Claude Code: Automated improvements
 
    - Automated formatting applied
    - Tests verified
    - Security checks passed"
    
    git push -u origin "$BRANCH"
    echo "$BRANCH"
}
 
wait_for_checks() {
    local pr_number=$1
    local max_attempts=30
    local attempt=0
    
    echo "Waiting for PR checks to complete..."
    
    while [ $attempt -lt $max_attempts ]; do
        # Get check status
        STATUS=$(gh pr checks "$pr_number" --json state -q '.[] | select(.state != "PENDING") | .state' | sort -u)
        
        if [[ "$STATUS" == "FAILURE" ]]; then
            echo "❌ PR checks failed"
            return 1
        elif [[ -z "$STATUS" ]] || [[ "$STATUS" == "SUCCESS" ]]; then
            # Verify all required checks passed
            for check in "${REQUIRED_CHECKS[@]}"; do
                if ! gh pr checks "$pr_number" | grep -q "$check.*✓"; then
                    echo "⏳ Waiting for $check..."
                    sleep 10
                    ((attempt++))
                    continue 2
                fi
            done
            echo "✅ All checks passed!"
            return 0
        fi
        
        sleep 10
        ((attempt++))
    done
    
    echo "⏱️  Timeout waiting for checks"
    return 1
}
 
main() {
    # Check for changes
    if git diff --quiet && git diff --staged --quiet; then
        echo "No changes to process"
        exit 0
    fi
    
    # Create branch and PR
    BRANCH=$(create_and_push_branch)
    
    PR_URL=$(gh pr create \
        --title "Claude Code: Automated improvements" \
        --body "This PR contains automated improvements by Claude Code.
        
### Automated Actions:
- [x] Code formatting
- [x] Test verification
- [x] Security scanning
 
### Review Requirements:
This PR will auto-merge if all checks pass." \
        --label "claude-code,automated" \
        --assignee "@me")
    
    PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
    echo "Created PR #$PR_NUMBER: $PR_URL"
    
    # Wait for and monitor checks
    if wait_for_checks "$PR_NUMBER"; then
        echo "🎉 Auto-merging PR #$PR_NUMBER"
        gh pr merge "$PR_NUMBER" --auto --squash --delete-branch
        
        # Switch back to main branch
        git checkout main
        git pull
    else
        echo "⚠️  Manual review required for PR #$PR_NUMBER"
        echo "View at: $PR_URL"
    fi
}
 
main

Exercise 2: DevOps Automation System ⭐⭐⭐⭐⭐

Problem: Create a complete DevOps automation system.

Solution:

#!/usr/bin/env python3
# Save as: .claude/hooks/devops_automation.py
 
import json
import sys
import subprocess
import asyncio
import logging
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Any
import yaml
 
# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('DevOpsAutomation')
 
class DevOpsAutomation:
    def __init__(self):
        self.config = self.load_config()
        self.metrics = {}
        
    def load_config(self) -> Dict:
        """Load DevOps configuration"""
        config_path = Path('.claude/devops_config.yaml')
        if config_path.exists():
            with open(config_path) as f:
                return yaml.safe_load(f)
        return {
            'repositories': [],
            'security_scan': True,
            'performance_threshold': 0.9,
            'deployment_strategy': 'blue-green',
            'rollback_on_failure': True
        }
    
    async def monitor_repositories(self) -> List[Dict]:
        """Monitor multiple repositories for changes"""
        changes = []
        for repo in self.config['repositories']:
            cmd = f"cd {repo['path']} && git fetch && git log HEAD..origin/main --oneline"
            result = await self.run_command(cmd)
            if result['stdout']:
                changes.append({
                    'repo': repo['name'],
                    'changes': result['stdout'].strip().split('\n')
                })
        return changes
    
    async def run_security_scan(self, path: str) -> Dict:
        """Run comprehensive security scanning"""
        scanners = [
            ('trivy', f'trivy fs --security-checks vuln {path}'),
            ('bandit', f'bandit -r {path} -f json'),
            ('safety', 'safety check --json'),
            ('npm-audit', 'npm audit --json')
        ]
        
        results = {}
        for scanner_name, cmd in scanners:
            try:
                result = await self.run_command(cmd)
                results[scanner_name] = {
                    'success': result['returncode'] == 0,
                    'output': result['stdout']
                }
            except Exception as e:
                results[scanner_name] = {
                    'success': False,
                    'error': str(e)
                }
        
        return results
    
    async def run_performance_tests(self) -> Dict:
        """Execute performance tests"""
        tests = {
            'load_test': 'k6 run scripts/load_test.js --out json=results.json',
            'benchmark': 'python scripts/benchmark.py',
            'memory_profile': 'python -m memory_profiler scripts/profile_memory.py'
        }
        
        results = {}
        for test_name, cmd in tests.items():
            result = await self.run_command(cmd)
            results[test_name] = {
                'success': result['returncode'] == 0,
                'metrics': self.parse_metrics(result['stdout'])
            }
        
        return results
    
    def generate_deployment_plan(self, changes: List[Dict]) -> Dict:
        """Generate deployment plan based on dependencies"""
        plan = {
            'strategy': self.config['deployment_strategy'],
            'steps': [],
            'rollback_points': []
        }
        
        # Analyze dependencies
        dependency_graph = self.build_dependency_graph(changes)
        
        # Topological sort for deployment order
        deployment_order = self.topological_sort(dependency_graph)
        
        for service in deployment_order:
            plan['steps'].append({
                'service': service,
                'actions': [
                    'backup_current_version',
                    'deploy_new_version',
                    'health_check',
                    'smoke_test'
                ],
                'rollback_point': f"checkpoint_{service}"
            })
            plan['rollback_points'].append(f"checkpoint_{service}")
        
        return plan
    
    async def execute_blue_green_deployment(self, plan: Dict) -> Dict:
        """Execute blue-green deployment"""
        results = {'deployments': []}
        
        for step in plan['steps']:
            service = step['service']
            logger.info(f"Deploying {service}...")
            
            # Deploy to blue environment
            blue_result = await self.deploy_to_environment(service, 'blue')
            
            if blue_result['success']:
                # Run tests on blue
                test_result = await self.test_environment(service, 'blue')
                
                if test_result['success']:
                    # Switch traffic
                    switch_result = await self.switch_traffic(service, 'blue')
                    
                    if switch_result['success']:
                        # Successful deployment
                        results['deployments'].append({
                            'service': service,
                            'status': 'success',
                            'environment': 'blue'
                        })
                        continue
            
            # Rollback on failure
            if self.config['rollback_on_failure']:
                await self.rollback_service(service)
                results['deployments'].append({
                    'service': service,
                    'status': 'rolled_back',
                    'reason': 'deployment_failed'
                })
                
                # Stop further deployments
                results['halted'] = True
                break
        
        return results
    
    async def monitor_production_metrics(self) -> Dict:
        """Monitor production metrics post-deployment"""
        metrics = {
            'response_time': [],
            'error_rate': [],
            'cpu_usage': [],
            'memory_usage': []
        }
        
        # Collect metrics for 5 minutes
        for _ in range(30):  # 30 * 10s = 5 minutes
            current_metrics = await self.collect_current_metrics()
            for key, value in current_metrics.items():
                metrics[key].append(value)
            
            # Check for anomalies
            if self.detect_anomalies(metrics):
                return {
                    'status': 'anomaly_detected',
                    'metrics': metrics,
                    'action': 'automatic_rollback'
                }
            
            await asyncio.sleep(10)
        
        return {
            'status': 'healthy',
            'metrics': metrics
        }
    
    async def automatic_rollback(self, service: str) -> Dict:
        """Perform automatic rollback"""
        logger.warning(f"Initiating automatic rollback for {service}")
        
        steps = [
            ('notify_team', self.notify_team(f"Rollback initiated for {service}")),
            ('switch_traffic', self.switch_traffic(service, 'green')),
            ('verify_health', self.test_environment(service, 'green')),
            ('cleanup', self.cleanup_failed_deployment(service))
        ]
        
        results = {}
        for step_name, coroutine in steps:
            result = await coroutine
            results[step_name] = result
            
            if not result.get('success'):
                logger.error(f"Rollback step {step_name} failed")
                break
        
        return results
    
    # Helper methods
    async def run_command(self, cmd: str) -> Dict:
        """Run shell command asynchronously"""
        proc = await asyncio.create_subprocess_shell(
            cmd,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE
        )
        stdout, stderr = await proc.communicate()
        
        return {
            'returncode': proc.returncode,
            'stdout': stdout.decode(),
            'stderr': stderr.decode()
        }
    
    def build_dependency_graph(self, changes: List[Dict]) -> Dict:
        """Build service dependency graph"""
        # Implementation depends on your architecture
        return {
            'frontend': ['api'],
            'api': ['auth', 'database'],
            'auth': ['database'],
            'database': []
        }
    
    def topological_sort(self, graph: Dict) -> List[str]:
        """Topological sort for deployment order"""
        visited = set()
        stack = []
        
        def dfs(node):
            visited.add(node)
            for neighbor in graph.get(node, []):
                if neighbor not in visited:
                    dfs(neighbor)
            stack.append(node)
        
        for node in graph:
            if node not in visited:
                dfs(node)
        
        return stack[::-1]
    
    def detect_anomalies(self, metrics: Dict) -> bool:
        """Detect anomalies in metrics"""
        # Simple threshold-based detection
        if metrics['error_rate'] and max(metrics['error_rate']) > 0.05:
            return True
        if metrics['response_time'] and max(metrics['response_time']) > 1000:
            return True
        return False
 
async def main():
    automation = DevOpsAutomation()
    
    try:
        # 1. Monitor repositories
        logger.info("Monitoring repositories...")
        changes = await automation.monitor_repositories()
        
        if not changes:
            logger.info("No changes detected")
            return 0
        
        # 2. Security scanning
        logger.info("Running security scans...")
        security_results = await automation.run_security_scan('.')
        
        vulnerabilities = sum(
            1 for r in security_results.values() 
            if not r['success']
        )
        if vulnerabilities > 0:
            logger.error(f"Security scan found {vulnerabilities} issues")
            return 2
        
        # 3. Performance testing
        logger.info("Running performance tests...")
        perf_results = await automation.run_performance_tests()
        
        # 4. Generate deployment plan
        logger.info("Generating deployment plan...")
        deployment_plan = automation.generate_deployment_plan(changes)
        
        # 5. Execute deployment
        logger.info("Executing blue-green deployment...")
        deployment_results = await automation.execute_blue_green_deployment(
            deployment_plan
        )
        
        # 6. Monitor production
        logger.info("Monitoring production metrics...")
        monitoring_results = await automation.monitor_production_metrics()
        
        if monitoring_results['status'] == 'anomaly_detected':
            # 7. Automatic rollback
            logger.warning("Anomaly detected, initiating rollback...")
            for deployment in deployment_results['deployments']:
                if deployment['status'] == 'success':
                    await automation.automatic_rollback(deployment['service'])
        
        logger.info("DevOps automation completed successfully")
        return 0
        
    except Exception as e:
        logger.error(f"Automation failed: {e}")
        return 1
 
if __name__ == '__main__':
    # Read Claude Code input
    data = json.load(sys.stdin)
    
    # Run automation
    exit_code = asyncio.run(main())
    sys.exit(exit_code)

Tips for All Exercises

  1. Start Simple: Test basic functionality before adding complexity
  2. Use Logging: Add debug logs to understand what’s happening
  3. Handle Errors: Always include error handling
  4. Test Thoroughly: Test with various inputs and edge cases
  5. Document: Add comments explaining complex logic

Remember: These solutions are starting points. Adapt them to your specific needs and environment!