claude-code hooks debugging troubleshooting best-practices tools

Debugging & Troubleshooting - Claude Code Hooks

Learn how to debug hooks effectively and solve common issues. This comprehensive guide covers debugging strategies, common problems with solutions, and advanced techniques to help you build reliable hooks.

Module Overview

  • Duration: 45 minutes
  • Prerequisites: Experience writing hooks (completed Introduction module)
  • Goal: Master debugging techniques and troubleshooting

What You’ll Learn

  • Effective logging strategies for hook debugging
  • How to diagnose and fix common hook issues
  • Advanced debugging techniques and tools
  • Performance profiling and optimization
  • Best practices for production debugging

Debugging Strategies

1. Basic Logging

The simplest debugging technique is adding log statements:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": ".*",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"[$(date)] Hook triggered: Tool=$CLAUDE_TOOL_NAME\" >> ~/.claude/debug.log"
          }
        ]
      }
    ]
  }
}

2. Verbose Logging Script

Create a comprehensive logging script:

#!/bin/bash
# Save as: .claude/hooks/debug_logger.sh
 
LOG_FILE="$HOME/.claude/hooks-debug.log"
JSON_INPUT=$(cat)
 
# Log everything
{
    echo "=== Hook Debug Log ==="
    echo "Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
    echo "Environment Variables:"
    echo "  CLAUDE_TOOL_NAME: $CLAUDE_TOOL_NAME"
    echo "  CLAUDE_FILE_PATHS: $CLAUDE_FILE_PATHS"
    echo "  CLAUDE_TOOL_INPUT: $CLAUDE_TOOL_INPUT"
    echo "  CLAUDE_TOOL_OUTPUT: $CLAUDE_TOOL_OUTPUT"
    echo "JSON Input:"
    echo "$JSON_INPUT" | jq '.'  # jq is a command-line JSON processor
    echo "Current Directory: $(pwd)"
    echo "User: $(whoami)"
    echo "===================="
} >> "$LOG_FILE"
 
# Pass through
echo "$JSON_INPUT"
exit 0

Note: This script uses jq, a lightweight command-line JSON processor. If not installed:

  • macOS: brew install jq
  • Ubuntu/Debian: sudo apt-get install jq
  • Other systems: Visit jq download page

3. Interactive Debugging

Use this pattern to pause execution for debugging:

#!/bin/bash
# Interactive debugging hook
 
# Check if debug mode is enabled
if [ "$CLAUDE_DEBUG" = "true" ]; then
    echo "🔍 Debug Mode - Hook paused" >&2
    echo "Press Enter to continue..." >&2
    read -r
    
    # Show current state
    echo "Current state:" >&2
    echo "Tool: $CLAUDE_TOOL_NAME" >&2
    echo "Files: $CLAUDE_FILE_PATHS" >&2
fi
 
# Your hook logic here

4. Error Capture Pattern

Comprehensive error handling:

#!/usr/bin/env python3
import json
import sys
import traceback
import logging
from datetime import datetime
 
# Setup logging
logging.basicConfig(
    filename='/tmp/claude_hooks_errors.log',
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
 
def main():
    try:
        # Read input
        data = json.load(sys.stdin)
        logging.info(f"Hook triggered: {data.get('hook_event_name')}")
        
        # Your hook logic here
        result = process_hook(data)
        
        # Success
        logging.info("Hook completed successfully")
        return 0
        
    except json.JSONDecodeError as e:
        logging.error(f"JSON parsing error: {e}")
        print(json.dumps({
            "continue": False,
            "stopReason": f"Invalid JSON input: {str(e)}"
        }))
        return 2
        
    except Exception as e:
        # Log full traceback
        logging.error(f"Unexpected error: {e}")
        logging.error(traceback.format_exc())
        
        # User-friendly error
        print(json.dumps({
            "continue": False,
            "stopReason": f"Hook error: {str(e)}"
        }))
        return 2
 
def process_hook(data):
    # Your implementation
    pass
 
if __name__ == '__main__':
    sys.exit(main())

Common Issues and Solutions

Issue 1: Hook Not Triggering

Symptoms: Hook doesn’t run when expected

Debugging Steps:

# 1. Check if hooks are loaded
/hooks
 
# 2. Test matcher pattern
echo "Edit" | grep -E "Edit|Write"
 
# 3. Add a simple test hook
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": ".*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Hook test working' >> /tmp/hook-test.txt"
          }
        ]
      }
    ]
  }
}

Issue 2: JSON Parsing Errors

Symptoms: “Invalid JSON” errors

Solution:

#!/bin/bash
# Safe JSON parsing
 
# Read stdin to variable first
JSON_INPUT=$(cat)
 
# Validate JSON (requires jq - see note above)
if ! echo "$JSON_INPUT" | jq empty 2>/dev/null; then
    echo "Invalid JSON received" >&2
    exit 1
fi
 
# Now safe to parse
TOOL_NAME=$(echo "$JSON_INPUT" | jq -r '.tool_name // ""')

Issue 3: Exit Code Confusion

Reference Table:

Exit CodeEffectUse Case
0Continue normallySuccess
1Warning (continue)Non-critical error
2Block operationValidation failure
OtherWarning (continue)Custom codes

Test Script:

#!/bin/bash
# Test different exit codes
 
case "$1" in
    block)
        echo "Blocking operation" >&2
        exit 2
        ;;
    warn)
        echo "Warning but continuing" >&2
        exit 1
        ;;
    *)
        echo "Success" >&2
        exit 0
        ;;
esac

Issue 4: Performance Problems

Symptoms: Hooks timeout or slow down Claude

Profiling Script:

#!/bin/bash
# Performance profiling hook
 
START_TIME=$(date +%s.%N)
 
# Your hook logic here
sleep 0.5  # Simulate work
 
END_TIME=$(date +%s.%N)
DURATION=$(echo "$END_TIME - $START_TIME" | bc)
 
# Log if slow
if (( $(echo "$DURATION > 1.0" | bc -l) )); then
    echo "Warning: Hook took ${DURATION}s" >> ~/.claude/slow-hooks.log
fi
 
exit 0

Optimization Tips:

  1. Use background tasks for long operations
  2. Cache expensive computations
  3. Limit file I/O operations
  4. Use appropriate timeouts

Note: The bc command (basic calculator) is used for floating-point math. It’s typically pre-installed on most Unix-like systems.

Issue 5: Environment Issues

Debug Environment Script:

#!/bin/bash
# Diagnose environment issues
 
{
    echo "=== Environment Diagnosis ==="
    echo "PATH: $PATH"
    echo "Shell: $SHELL"
    echo "Working Directory: $(pwd)"
    echo "Available Commands:"
    # Check for common commands used in hooks
    for cmd in jq git npm python3 node bc grep sed awk; do
        if command -v $cmd &> /dev/null; then
            echo "  ✓ $cmd: $(command -v $cmd)"
        else
            echo "  ✗ $cmd: NOT FOUND"
        fi
    done
    echo "========================="
} >> ~/.claude/env-diagnosis.log

Advanced Debugging Techniques

1. Hook Test Framework

Create a test framework for your hooks:

#!/usr/bin/env python3
"""
Hook testing framework
Save as: .claude/hooks/test_framework.py
"""
 
import json
import subprocess
import tempfile
from pathlib import Path
 
class HookTester:
    def __init__(self, hook_command):
        self.hook_command = hook_command
    
    def test(self, input_data, expected_exit_code=0):
        """Test a hook with given input"""
        with tempfile.NamedTemporaryFile(mode='w', suffix='.json') as f:
            json.dump(input_data, f)
            f.flush()
            
            result = subprocess.run(
                self.hook_command,
                stdin=open(f.name),
                capture_output=True,
                text=True,
                shell=True
            )
            
            return {
                'exit_code': result.returncode,
                'stdout': result.stdout,
                'stderr': result.stderr,
                'success': result.returncode == expected_exit_code
            }
 
# Example usage
if __name__ == '__main__':
    tester = HookTester('python3 my_hook.py')
    
    # Test cases
    test_cases = [
        {
            'name': 'Valid file edit',
            'input': {
                'tool_name': 'Edit',
                'tool_input': {'file_path': '/tmp/test.txt'}
            },
            'expected': 0
        },
        {
            'name': 'Blocked file edit',
            'input': {
                'tool_name': 'Edit',
                'tool_input': {'file_path': '.env'}
            },
            'expected': 2
        }
    ]
    
    for test in test_cases:
        result = tester.test(test['input'], test['expected'])
        status = "✓" if result['success'] else "✗"
        print(f"{status} {test['name']}: exit={result['exit_code']}")

2. Real-time Monitoring

Monitor hooks in real-time:

#!/bin/bash
# Real-time hook monitor
# Save as: monitor_hooks.sh
 
LOG_FILE="$HOME/.claude/hooks-debug.log"
 
# Clear screen
clear
 
echo "Claude Code Hooks Monitor"
echo "========================"
echo "Watching: $LOG_FILE"
echo "Press Ctrl+C to exit"
echo ""
 
# Monitor log file
tail -f "$LOG_FILE" | while read -r line; do
    # Color code by event type
    if [[ "$line" =~ "PreToolUse" ]]; then
        echo -e "\033[34m$line\033[0m"  # Blue
    elif [[ "$line" =~ "PostToolUse" ]]; then
        echo -e "\033[32m$line\033[0m"  # Green
    elif [[ "$line" =~ "ERROR" ]]; then
        echo -e "\033[31m$line\033[0m"  # Red
    else
        echo "$line"
    fi
done

3. Hook Benchmarking

Benchmark hook performance:

#!/usr/bin/env python3
"""
Benchmark hook performance
"""
 
import time
import statistics
import subprocess
import json
 
def benchmark_hook(hook_cmd, test_input, iterations=10):
    """Benchmark a hook's performance"""
    times = []
    
    for i in range(iterations):
        start = time.time()
        
        proc = subprocess.run(
            hook_cmd,
            input=json.dumps(test_input),
            capture_output=True,
            text=True,
            shell=True
        )
        
        duration = time.time() - start
        times.append(duration)
        
        print(f"Run {i+1}: {duration:.3f}s")
    
    print(f"\nResults for {hook_cmd}:")
    print(f"  Mean: {statistics.mean(times):.3f}s")
    print(f"  Median: {statistics.median(times):.3f}s")
    print(f"  Std Dev: {statistics.stdev(times):.3f}s")
    print(f"  Min: {min(times):.3f}s")
    print(f"  Max: {max(times):.3f}s")
 
# Usage
if __name__ == '__main__':
    test_data = {
        'tool_name': 'Edit',
        'tool_input': {'file_path': 'test.py'}
    }
    
    benchmark_hook('python3 my_hook.py', test_data)

Troubleshooting Checklist

Pre-flight Checks

  • JSON syntax is valid
  • Hook script has execute permissions
  • Required commands are in PATH
  • Environment variables are set
  • Matcher pattern is correct

During Development

  • Start with simple echo hooks
  • Test scripts manually first
  • Check logs immediately
  • Use appropriate exit codes
  • Handle errors gracefully

Production Issues

  • Check Claude Code version
  • Verify settings.json location
  • Review recent changes
  • Check system resources
  • Test in isolation

Best Practices Summary

  1. Always log errors - Future you will thank you
  2. Test hooks independently - Don’t test in production
  3. Use version control - Track hook changes
  4. Document your hooks - Explain what and why
  5. Monitor performance - Keep hooks fast
  6. Handle edge cases - Expect the unexpected

What’s Next?

Now that you’ve mastered debugging techniques, continue your learning journey:

  1. Security Best Practices - Learn to write secure hooks that protect sensitive data
  2. Performance Optimization - Make your hooks fast and efficient
  3. Exercise Solutions - Review working examples from workshop exercises

Workshop Content

Core Documentation

Development Resources

Quick Debug Commands

# View recent hook activity
tail -f ~/.claude/hooks-debug.log
 
# Check hook configuration (requires jq)
cat .claude/settings.json | jq '.hooks'
 
# Test JSON parsing (requires jq)
echo '{"test": "data"}' | jq '.'
 
# Find hook errors
grep -i error ~/.claude/*.log
 
# Monitor system resources (Linux)
top -p $(pgrep -f claude)
 
# Monitor system resources (macOS)
top -pid $(pgrep claude)
 
# Alternative: Use htop for better visualization (if installed)
htop -p $(pgrep -f claude)

External Tool References

This workshop uses several command-line tools:

  • jq - Command-line JSON processor for parsing and manipulating JSON data

    • Install: brew install jq (macOS) or apt-get install jq (Linux)
    • Documentation
  • bc - Basic calculator for floating-point arithmetic in bash scripts

    • Usually pre-installed on Unix-like systems
    • Manual
  • grep/sed/awk - Text processing utilities

    • Pre-installed on most systems
    • Used for searching and manipulating text output