--- name: log-inspection description: "Inspect and query hook event logs. Subcommands: sessions, timeline, trace, agents, team, stats, coverage, daily." user-invocable: true argument-hint: " [options]" allowed-tools: Read, Bash, Glob, Grep --- # Log Inspection Query and inspect observability hook logs from JSONL files. ## Arguments Subcommand is the first argument: - **`sessions`** -- List all sessions with event counts and duration - **`timeline [session-id]`** -- Chronological event list with seq numbers - **`trace `** -- Show pre/post pairing for a tool invocation - **`agents`** -- Subagent lifecycle (start/stop/duration) - **`team`** -- TeammateIdle and TaskCompleted activity - **`stats`** -- Aggregate statistics (event breakdown, tool usage, corruption count) - **`coverage`** -- Which of 14 event types have been observed - **`daily`** -- Show daily summaries from sessions-index.jsonl (v1.6.0) ## Log File Location Look for log files at: ```text {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: ```text 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. ## Workflow ### Step 1: Locate Log Files Find all hook log files in the project: ```text 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`. ### Step 2: Execute Subcommand #### `sessions` Parse `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: ```text 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 ` Find pretooluse and posttooluse/posttoolusefailure entries with the given tool_use_id. Display the pair showing timing and success/failure. #### `agents` First 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 | |----------|------|---------------|---------------|----------| #### `team` First 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 | |------|------|----------|------|--------------| #### `stats` Parse all entries and compute: - **Event breakdown**: Count of each event type - **Tool usage**: Most frequently used tools (from pretooluse events) - **Error rate**: posttoolusefailure count / total tool uses - **Avg duration_ms**: Average hook processing time - **Corruption count**: Lines that fail JSON parsing - **Unique sessions**: Count of distinct session_id values - **Seq range**: Min and max seq values Use Bash with Python one-liners for aggregation. For `.jsonl.gz` files, use `gzip.open` to read: ```bash 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. #### `coverage` Check which of the 14 event types have been captured. Display: ```text 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`. #### `daily` Parse `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. ### Step 3: Format Output Present results in a clear, readable format. Use markdown tables where appropriate. Include the log file path and date range in the header. ## DuckDB Queries (Advanced) For large log files, suggest DuckDB for analytical queries: ```sql -- 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; ``` ## Usage Examples ```bash # 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 ```