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 0Note: 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 here4. 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 Code | Effect | Use Case |
|---|---|---|
| 0 | Continue normally | Success |
| 1 | Warning (continue) | Non-critical error |
| 2 | Block operation | Validation failure |
| Other | Warning (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
;;
esacIssue 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 0Optimization Tips:
- Use background tasks for long operations
- Cache expensive computations
- Limit file I/O operations
- Use appropriate timeouts
Note: The
bccommand (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.logAdvanced 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
done3. 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
- Always log errors - Future you will thank you
- Test hooks independently - Don’t test in production
- Use version control - Track hook changes
- Document your hooks - Explain what and why
- Monitor performance - Keep hooks fast
- Handle edge cases - Expect the unexpected
What’s Next?
Now that you’ve mastered debugging techniques, continue your learning journey:
- Security Best Practices - Learn to write secure hooks that protect sensitive data
- Performance Optimization - Make your hooks fast and efficient
- Exercise Solutions - Review working examples from workshop exercises
Related Resources
Workshop Content
- Workshop Overview - Return to the main workshop page
- Workshop Introduction - Review the basics
- Solutions & Examples - Complete working examples with explanations
Core Documentation
- Hooks Documentation Hub - Complete hooks reference
- Hooks Tutorials - Step-by-step tutorials
- Hook Design Patterns - Common patterns and best practices
- Troubleshooting Guide - General hooks troubleshooting
Development Resources
- Testing Documentation - Testing hooks and Claude Code
- Performance Deep Dive - Comprehensive performance guide
- Security Deep Dive - In-depth security practices
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) orapt-get install jq(Linux) - Documentation
- Install:
-
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