plugins/claude-code-observability/skills/log-inspection/SKILL.md
Inspect and query hook event logs. Subcommands: sessions, timeline, trace, agents, team, stats, coverage, daily.
npx skillsauth add melodic-software/claude-code-plugins log-inspectionInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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
development
Search Milan Jovanovic's .NET blog for Clean Architecture, DDD, CQRS, EF Core, and ASP.NET Core patterns. Use for finding applicable patterns, code examples, and architecture guidance. Invoke when working with .NET projects that could benefit from proven architectural patterns.
tools
Install and configure Data API Builder (DAB) for production SQL Server MCP access with RBAC
tools
Manage MssqlMcp servers - status, rebuild, and upstream updates
tools
Developer environment setup guides for Windows, macOS, Linux, and WSL. Use when setting up development machines, installing tools, configuring environments, or following platform-specific setup guides. Covers package management, shell/terminal, code editors, AI tooling, containerization, databases, and more.