Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

README.md

Claude Code Observability Plugin

Observability hooks for Claude Code: comprehensive event logging, performance metrics, and session diagnostics.

Overview

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

Installation

/plugin install claude-code-observability@claude-code-plugins

Quick Start

Enable 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.

Configuration

All configuration is via environment variables (set in .claude/settings.json env section):

Master Control

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)

Per-Event Toggles

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_POSTTOOLUSEFAILURE_ENABLED Log PostToolUseFailure events 1
CLAUDE_HOOK_LOG_USERPROMPTSUBMIT_ENABLED Log UserPromptSubmit events 1
CLAUDE_HOOK_LOG_STOP_ENABLED Log Stop events 1
CLAUDE_HOOK_LOG_SUBAGENTSTART_ENABLED Log SubagentStart 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
CLAUDE_HOOK_LOG_TEAMMATEIDLE_ENABLED Log TeammateIdle events 1
CLAUDE_HOOK_LOG_TASKCOMPLETED_ENABLED Log TaskCompleted events 1

Verbosity Levels

Variable Description Default
CLAUDE_HOOK_LOG_VERBOSITY Output detail level summary

Verbosity options:

  • minimal (~200 bytes/entry): timestamp, event, session_id, tool_name, duration_ms
  • summary (~500 bytes/entry): minimal + event-specific fields (see below), _v, extra_fields for unknown fields
  • full (1-50KB/entry): Complete input payload with all details

Event-Specific Fields (Summary Mode)

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), permission_suggestions (summarized)
PostToolUse tool_name, tool_input (summarized), tool_response (summarized), tool_use_id, tool_success
PostToolUseFailure tool_name, tool_input (summarized), tool_use_id, error (truncated), is_interrupt
Notification message, title, notification_type
UserPromptSubmit prompt (truncated to 200 chars)
Stop stop_hook_active
SubagentStart agent_id, agent_type, parent_agent_id
SubagentStop stop_hook_active, agent_id, agent_type, agent_transcript_path, parent_agent_id
PreCompact trigger, custom_instructions (truncated)
SessionStart source, model, agent_type
SessionEnd reason
TeammateIdle teammate_name, team_name
TaskCompleted task_id, task_subject, task_description (truncated), teammate_name, team_name

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.

Log Rotation

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

Log Retention and Size Limits

Variable Description Default
CLAUDE_HOOK_LOG_RETENTION_MAX_AGE_DAYS Default cleanup retention 30
CLAUDE_HOOK_LOG_RETENTION_DAYS Delete logs older than N days (auto, on sessionstart) 30
CLAUDE_HOOK_LOG_COMPRESS_AFTER_DAYS Gzip logs older than N days (0 to disable) 7
CLAUDE_HOOK_LOG_MAX_TOTAL_SIZE_MB Max total log directory size (0 to disable) 200

Retention and compression run automatically on each sessionstart. The /cleanup-hook-logs skill uses CLAUDE_HOOK_LOG_RETENTION_MAX_AGE_DAYS for manual cleanup.

Log Format

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:

  1. CLAUDE_HOOK_LOG_DIR environment variable (explicit override)
  2. CLAUDE_PROJECT_DIR/.claude/logs/hooks (project-local, default)
  3. Current working directory fallback

Sample Log Entries

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,"_v":"1.9.0","model":"claude-opus-4-6","transcript_path":"/path/to/transcript.jsonl","tool_use_id":"toolu_01xyz","tool_input":{"_keys":["command","cwd"],"_type":"dict_summary"},"cwd":"/workspace","permission_mode":"bypassPermissions","turn_id":3}

Summary with unknown field (future-proofing):

{"timestamp":"2025-12-11T16:13:15.464Z","event":"sessionstart","session_id":"abc-123","duration_ms":1.23,"_v":"1.9.0","model":"claude-opus-4-6","source":"startup","turn_id":0,"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"}}

Events Logged

All 14 Claude Code hook events are captured (all run async):

Event Description
PreToolUse Before tool execution
PermissionRequest When permission dialog shown
PostToolUse After tool completion
PostToolUseFailure After a tool call fails
Notification When Claude sends notifications
UserPromptSubmit When user submits prompt
Stop When main agent finishes
SubagentStart When a subagent is spawned
SubagentStop When subagent finishes
PreCompact Before compact operation
SessionStart Session start/resume
SessionEnd Session end
TeammateIdle When an agent team teammate is about to go idle
TaskCompleted When a task is marked completed

Querying Logs

jq Examples

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 -c

Find 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 -rn

DuckDB Examples (10-100x faster for large logs)

-- Event breakdown
SELECT event, count(*) as cnt
FROM read_json_auto('.claude/logs/hooks/events-*.jsonl')
GROUP BY event ORDER BY cnt DESC;

-- Tool usage with timing
SELECT tool_name, count(*) as uses, avg(duration_ms) as avg_ms
FROM read_json_auto('.claude/logs/hooks/events-*.jsonl')
WHERE event = 'pretooluse'
GROUP BY tool_name ORDER BY uses DESC;

-- Events per conversation turn
SELECT turn_id, count(*) as events, count(DISTINCT tool_name) as unique_tools
FROM read_json_auto('.claude/logs/hooks/events-*.jsonl')
WHERE turn_id > 0
GROUP BY turn_id ORDER BY turn_id;

-- Error breakdown by category
SELECT error_category, count(*) as cnt
FROM read_json_auto('.claude/logs/hooks/events-*.jsonl')
WHERE event = 'posttoolusefailure'
GROUP BY error_category;

-- Session summaries from index
SELECT session_id, duration_seconds, error_count
FROM read_json_auto('.claude/logs/hooks/sessions-index.jsonl')
WHERE type = 'session_summary';

-- Read compressed logs (DuckDB handles .gz natively)
SELECT event, count(*) as cnt
FROM read_json_auto('.claude/logs/hooks/events-*.jsonl.gz')
GROUP BY event ORDER BY cnt DESC;

Schema Version Tracking

Log entries include a _v field (currently 1.9.0) across all verbosity levels and session index entries. 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

Skills

/claude-code-observability:audit-hook-schema

Detect drift between the hook dispatcher's schema and official Claude Code documentation:

# Basic audit - compare implementation vs official docs
/claude-code-observability:audit-hook-schema

# Machine-readable output for CI
/claude-code-observability:audit-hook-schema --json

# Generate updated registry code
/claude-code-observability:audit-hook-schema --fix

# Detailed output
/claude-code-observability:audit-hook-schema --verbose

The audit skill:

  1. Loads official hook documentation via docs-management skill
  2. Parses the current EVENT_FIELD_REGISTRY from hook_dispatcher.py
  3. Compares documented fields vs implemented fields
  4. Reports coverage percentages and missing fields

/claude-code-observability:log-inspection

Inspect and query hook event logs:

# List sessions
/claude-code-observability:log-inspection sessions

# Event type coverage
/claude-code-observability:log-inspection coverage

# Full statistics
/claude-code-observability:log-inspection stats

# Timeline for a session
/claude-code-observability:log-inspection timeline

/cleanup-hook-logs

Delete old log files to free disk space:

# Delete logs older than 30 days (default)
python plugins/claude-code-observability/hooks/cleanup_logs.py

# Delete logs older than 7 days
python plugins/claude-code-observability/hooks/cleanup_logs.py --days 7

# Preview what would be deleted
python plugins/claude-code-observability/hooks/cleanup_logs.py --dry-run

Recommended Configuration

Add 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_DAYS": "30",
    "CLAUDE_HOOK_LOG_COMPRESS_AFTER_DAYS": "7",
    "CLAUDE_HOOK_LOG_MAX_TOTAL_SIZE_MB": "200"
  }
}

Uninstallation

To completely disable all observability hooks (zero overhead):

/plugin uninstall claude-code-observability

Dependencies

  • Python 3.6+ (uses only standard library)
  • jq (recommended for log queries)

Design Principles

  1. Never block Claude Code - All exceptions caught, always exits 0
  2. Fail silently - Logging failures don't impact user experience
  3. No dependencies - Uses only Python standard library
  4. Cross-platform - Works on Windows, macOS, and Linux
  5. Configurable - All settings via environment variables

License

MIT