Help us improve
Share bugs, ideas, or general feedback.
From claude-code-observability
Inspects and queries Claude Code hook event logs from .claude/logs JSONL files. Subcommands list sessions, timelines, tool traces, agent lifecycles, team activity, stats, coverage, and daily summaries.
npx claudepluginhub melodic-software/claude-code-plugins --plugin claude-code-observabilityHow this skill is triggered — by the user, by Claude, or both
Slash command
/claude-code-observability:log-inspectionThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Query and inspect observability hook logs from JSONL files.
Queries and analyzes JSONL agent event logs to debug behavior, find slow tool calls, trace decisions, and summarize session performance.
Locates, lists, filters, and extracts structured data from Claude Code native session logs. Supports both single and multiple session analysis.
Queries and analyzes JSONL agent event logs to debug behavior, find slow tool calls, trace decisions, and evaluate session performance.
Share bugs, ideas, or general feedback.
Query and inspect observability hook logs from JSONL files.
Subcommand is the first argument:
sessions -- List all sessions with event counts and durationtimeline [session-id] -- Chronological event list with seq numberstrace <tool-use-id> -- Show pre/post pairing for a tool invocationagents -- Subagent lifecycle (start/stop/duration)team -- TeammateIdle and TaskCompleted activitystats -- Aggregate statistics (event breakdown, tool usage, corruption count)coverage -- Which of 14 event types have been observeddaily -- Show daily summaries from sessions-index.jsonl (v1.6.0)Look for log files at:
{project}/.claude/logs/hooks/events-{YYYY-MM-DD}.jsonl
{project}/.claude/logs/hooks/events-{YYYY-MM-DD}.jsonl.gz (compressed cold logs)
Use Glob to find all available log files:
Glob(".claude/logs/hooks/events-*.jsonl")
Glob(".claude/logs/hooks/events-*.jsonl.gz")
Note: Files older than 7 days are automatically compressed to .jsonl.gz (configurable via CLAUDE_HOOK_LOG_COMPRESS_AFTER_DAYS). DuckDB reads .jsonl.gz natively; use zcat | jq on Unix for manual inspection.
Find all hook log files in the project:
Glob(".claude/logs/hooks/events-*.jsonl")
Glob(".claude/logs/hooks/events-*.jsonl.gz")
Glob(".claude/logs/hooks/sessions-index.jsonl")
If no log files exist, inform the user that logging must be enabled via CLAUDE_HOOK_LOG_EVENTS_ENABLED=1.
sessionsParse sessions-index.jsonl if it exists. Use the type field to discriminate entries (session_start, session_end, agent_start, agent_stop). For backward compatibility, entries without type are inferred from the presence of started_at (session_start) or ended_at (session_end). Otherwise, scan event log files for sessionstart and sessionend events. Display:
| Session ID | Started | Ended | Model | Events | Duration |
|---|
The enriched index also includes cwd, transcript_path, model, and agent_type on session_start entries.
timeline [session-id]Read the log file and display events in seq order. If session-id is provided, filter to that session. Display:
seq timestamp event tool_name pid
1 2026-02-15T18:13:23 sessionstart - 12345
2 2026-02-15T18:13:24 userpromptsubmit - 12345
3 2026-02-15T18:13:25 pretooluse Read 12346
4 2026-02-15T18:13:25 posttooluse Read 12346
trace <tool-use-id>Find pretooluse and posttooluse/posttoolusefailure entries with the given tool_use_id. Display the pair showing timing and success/failure.
agentsFirst check sessions-index.jsonl for agent_start and agent_stop entries (direct lookup, no event scanning needed). Fall back to scanning event log files for subagentstart and subagentstop events if index is missing. Display:
| Agent ID | Type | Started (seq) | Stopped (seq) | Duration |
|---|
teamFirst check sessions-index.jsonl for task_completed and teammate_idle entries (direct lookup, added in v1.8.0). Fall back to scanning event log files for teammateidle and taskcompleted events if index entries are missing. Display:
| Time | Type | Teammate | Team | Task Subject |
|---|
statsParse all entries and compute:
Use Bash with Python one-liners for aggregation. For .jsonl.gz files, use gzip.open to read:
python3 -c "
import json, gzip, glob
from collections import Counter
events = Counter()
tools = Counter()
errors = 0
corrupt = 0
durations = []
sessions = set()
def read_lines(path):
if path.endswith('.gz'):
with gzip.open(path, 'rt', encoding='utf-8') as f:
return f.readlines()
with open(path, 'r', encoding='utf-8') as f:
return f.readlines()
for path in sorted(glob.glob('.claude/logs/hooks/events-*.jsonl') + glob.glob('.claude/logs/hooks/events-*.jsonl.gz')):
for line in read_lines(path):
line = line.strip()
if not line:
continue
try:
e = json.loads(line)
events[e.get('event','')] += 1
if 'tool_name' in e:
tools[e['tool_name']] += 1
if e.get('event') == 'posttoolusefailure':
errors += 1
durations.append(e.get('duration_ms', 0))
sessions.add(e.get('session_id',''))
except json.JSONDecodeError:
corrupt += 1
print(f'Events: {dict(events)}')
print(f'Top tools: {tools.most_common(10)}')
print(f'Error rate: {errors}/{events.get(\"pretooluse\",0)} tool uses')
print(f'Avg duration: {sum(durations)/len(durations):.2f}ms' if durations else 'No entries')
print(f'Corrupted lines: {corrupt}')
print(f'Unique sessions: {len(sessions)}')
"
Note: hook_event_name was removed from entries in v1.4.0 and fully suppressed from extra_fields in v1.7.0.
coverageCheck which of the 14 event types have been captured. Display:
Event Coverage Report
=====================
[x] pretooluse (142 events)
[x] posttooluse (140 events)
[x] userpromptsubmit (8 events)
[ ] precompact (0 events)
...
Coverage: 12/14 event types observed
The 14 required types are: pretooluse, posttooluse, posttoolusefailure, permissionrequest, notification, userpromptsubmit, stop, subagentstart, subagentstop, precompact, sessionstart, sessionend, teammateidle, taskcompleted.
dailyParse sessions-index.jsonl for daily_summary entries (v1.6.0). Display:
| Date | Events | Sessions | Errors | Avg Duration |
|---|
Daily summaries are generated automatically on the first sessionstart of each day for the previous day's logs.
Present results in a clear, readable format. Use markdown tables where appropriate. Include the log file path and date range in the header.
For large log files, suggest DuckDB for analytical queries:
-- Load JSONL directly
SELECT event, count(*) as cnt
FROM read_json_auto('.claude/logs/hooks/events-2026-02-15.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-2026-02-15.jsonl')
WHERE event = 'pretooluse'
GROUP BY tool_name ORDER BY uses DESC;
-- Find corrupted entries (seq gaps)
SELECT seq, lag(seq) OVER (ORDER BY seq) as prev_seq
FROM read_json_auto('.claude/logs/hooks/events-2026-02-15.jsonl')
WHERE seq - lag(seq) OVER (ORDER BY seq) > 1;
# List sessions
/claude-code-observability:log-inspection sessions
# Timeline for current session
/claude-code-observability:log-inspection timeline
# Trace a specific tool call
/claude-code-observability:log-inspection trace toolu_01abc123
# Event type coverage
/claude-code-observability:log-inspection coverage
# Full statistics
/claude-code-observability:log-inspection stats