Observability hooks for Claude Code: comprehensive event logging, performance metrics, and session diagnostics.
This plugin provides full observability into Claude Code hook execution without modifying behavior. All hooks are purely observational - they log events but never block operations or enforce rules.
Key Benefits:
- Full audit trail - Track all Claude Code interactions
- Performance analysis - Monitor hook execution timing via
duration_ms - Configurable verbosity - Choose between minimal, summary, or full logging
- Size-based rotation - Automatic log rotation to prevent unbounded growth
- Optional by design - Enable/disable via environment variables
/plugin install claude-code-observability@claude-code-pluginsEnable logging by setting the master toggle in your .claude/settings.json:
{
"env": {
"CLAUDE_HOOK_LOG_EVENTS_ENABLED": "1"
}
}Logs are written to: <project>/.claude/logs/hooks/events-YYYY-MM-DD.jsonl
You can override the log directory with CLAUDE_HOOK_LOG_DIR environment variable.
All configuration is via environment variables (set in .claude/settings.json env section):
| Variable | Description | Default |
|---|---|---|
CLAUDE_HOOK_LOG_EVENTS_ENABLED |
Master enable/disable | 0 (disabled) |
CLAUDE_HOOK_LOG_DEBUG |
Show errors in stderr | 0 |
CLAUDE_HOOK_LOG_DIR |
Custom log directory | (project-local) |
Disable specific events while keeping others enabled:
| Variable | Description | Default |
|---|---|---|
CLAUDE_HOOK_LOG_PRETOOLUSE_ENABLED |
Log PreToolUse events | 1 |
CLAUDE_HOOK_LOG_POSTTOOLUSE_ENABLED |
Log PostToolUse events | 1 |
CLAUDE_HOOK_LOG_USERPROMPTSUBMIT_ENABLED |
Log UserPromptSubmit events | 1 |
CLAUDE_HOOK_LOG_STOP_ENABLED |
Log Stop events | 1 |
CLAUDE_HOOK_LOG_SUBAGENTSTOP_ENABLED |
Log SubagentStop events | 1 |
CLAUDE_HOOK_LOG_SESSIONSTART_ENABLED |
Log SessionStart events | 1 |
CLAUDE_HOOK_LOG_SESSIONEND_ENABLED |
Log SessionEnd events | 1 |
CLAUDE_HOOK_LOG_PRECOMPACT_ENABLED |
Log PreCompact events | 1 |
CLAUDE_HOOK_LOG_NOTIFICATION_ENABLED |
Log Notification events | 1 |
CLAUDE_HOOK_LOG_PERMISSIONREQUEST_ENABLED |
Log PermissionRequest events | 1 |
| Variable | Description | Default |
|---|---|---|
CLAUDE_HOOK_LOG_VERBOSITY |
Output detail level | summary |
Verbosity options:
minimal(~200 bytes/entry): timestamp, event, session_id, tool_name, duration_mssummary(~500 bytes/entry): minimal + event-specific fields (see below), schema_version, extra_fields for unknown fieldsfull(1-50KB/entry): Complete input payload with all details
Summary mode now captures event-specific fields based on a schema registry:
| Event | Fields Captured |
|---|---|
| PreToolUse | tool_name, tool_input (summarized), tool_use_id |
| PermissionRequest | tool_name, tool_input (summarized), tool_use_id |
| PostToolUse | tool_name, tool_input (summarized), tool_response (summarized), tool_use_id, tool_success |
| Notification | message, notification_type |
| UserPromptSubmit | prompt (truncated to 200 chars) |
| Stop | stop_hook_active |
| SubagentStop | stop_hook_active |
| PreCompact | trigger, custom_instructions (truncated) |
| SessionStart | source |
| SessionEnd | reason |
Large field summarization:
- Strings > 200 chars: Truncated with "..." suffix
- Dicts: Summarized to
{_keys: [...], _type: "dict_summary"} - Lists: Summarized to
{_count: N, _type: "list_summary"}
Future-proofing: Any field in the hook input that isn't in the registry is captured in an extra_fields object. This ensures new Claude Code fields are automatically logged without code changes.
| Variable | Description | Default |
|---|---|---|
CLAUDE_HOOK_LOG_ROTATION_ENABLED |
Enable size-based rotation | 1 |
CLAUDE_HOOK_LOG_ROTATION_MAX_SIZE_MB |
Max file size before rotation | 10 |
When a log file exceeds the max size, a new file is created: events-2025-12-11-001.jsonl
| Variable | Description | Default |
|---|---|---|
CLAUDE_HOOK_LOG_RETENTION_MAX_AGE_DAYS |
Default cleanup retention | 30 |
Used by the /cleanup-hook-logs command.
Logs are stored as JSON Lines (one JSON object per line) in daily files.
Default Location: <project>/.claude/logs/hooks/events-YYYY-MM-DD.jsonl
Log Directory Priority:
CLAUDE_HOOK_LOG_DIRenvironment variable (explicit override)CLAUDE_PROJECT_DIR/.claude/logs/hooks(project-local, default)- Current working directory fallback
Minimal verbosity:
{"timestamp":"2025-12-11T16:13:15.464Z","event":"pretooluse","session_id":"abc-123","tool_name":"Bash","duration_ms":2.45}Summary verbosity:
{"timestamp":"2025-12-11T16:13:15.464Z","event":"pretooluse","session_id":"abc-123","tool_name":"Bash","duration_ms":2.45,"schema_version":"1.0.0","transcript_path":"/path/to/transcript.jsonl","tool_use_id":"toolu_01xyz","tool_input":{"_keys":["command","cwd"],"_type":"dict_summary"},"cwd":"/workspace","permission_mode":"bypassPermissions"}Summary with unknown field (future-proofing):
{"timestamp":"2025-12-11T16:13:15.464Z","event":"sessionstart","session_id":"abc-123","duration_ms":1.23,"schema_version":"1.0.0","source":"startup","extra_fields":{"new_claude_field":"some value"}}Full verbosity:
{"timestamp":"2025-12-11T16:13:15.464Z","event":"pretooluse","session_id":"unknown","input":{"session_id":"abc-123","tool_name":"Bash","tool_input":{"command":"git status"},...},"duration_ms":2.45,"environment":{"CLAUDE_PROJECT_DIR":"/workspace"}}All 10 Claude Code hook events are captured:
| Event | Description |
|---|---|
| PreToolUse | Before tool execution |
| PermissionRequest | When permission dialog shown |
| PostToolUse | After tool completion |
| Notification | When Claude sends notifications |
| UserPromptSubmit | When user submits prompt |
| Stop | When main agent finishes |
| SubagentStop | When subagent finishes |
| PreCompact | Before compact operation |
| SessionStart | Session start/resume |
| SessionEnd | Session end |
View today's events:
cat .claude/logs/hooks/events-$(date +%Y-%m-%d).jsonl | jq .Count events by type:
cat .claude/logs/hooks/events-*.jsonl | jq -r '.event' | sort | uniq -cFind all Write tool uses:
cat .claude/logs/hooks/events-*.jsonl | jq 'select(.tool_name == "Write")'Find slow operations (> 10ms):
cat .claude/logs/hooks/events-*.jsonl | jq 'select(.duration_ms > 10)'Summary of events by session:
cat .claude/logs/hooks/events-*.jsonl | jq -r '.session_id' | sort | uniq -c | sort -rnLog entries in summary mode include a schema_version field (currently 1.0.0). This helps track which version of the field registry generated each entry, enabling:
- Backward compatibility: Parse logs knowing which fields to expect
- Drift detection: Identify when the schema changed
- Migration support: Update log parsers when schema evolves
Detect drift between the hook dispatcher's schema and official Claude Code documentation:
# Basic audit - compare implementation vs official docs
/audit-hook-schema
# Machine-readable output for CI
/audit-hook-schema --json
# Generate updated registry code
/audit-hook-schema --fix
# Detailed output
/audit-hook-schema --verboseThe audit command:
- Loads official hook documentation via
docs-managementskill - Parses the current
EVENT_FIELD_REGISTRYfromhook_dispatcher.py - Compares documented fields vs implemented fields
- Reports coverage percentages and missing fields
Delete old log files to free disk space:
# Delete logs older than 30 days (default)
/cleanup-hook-logs
# Delete logs older than 7 days
/cleanup-hook-logs 7
# Preview what would be deleted
/cleanup-hook-logs --dry-runAdd to your .claude/settings.json:
{
"env": {
"CLAUDE_HOOK_LOG_EVENTS_ENABLED": "1",
"CLAUDE_HOOK_LOG_VERBOSITY": "summary",
"CLAUDE_HOOK_LOG_ROTATION_ENABLED": "1",
"CLAUDE_HOOK_LOG_ROTATION_MAX_SIZE_MB": "10",
"CLAUDE_HOOK_LOG_RETENTION_MAX_AGE_DAYS": "30"
}
}To completely disable all observability hooks (zero overhead):
/plugin uninstall claude-code-observability- Python 3.6+ (uses only standard library)
- jq (recommended for log queries)
- Never block Claude Code - All exceptions caught, always exits 0
- Fail silently - Logging failures don't impact user experience
- No dependencies - Uses only Python standard library
- Cross-platform - Works on Windows, macOS, and Linux
- Configurable - All settings via environment variables
MIT