| name | transcript-analyzer |
| description | Analyze Claude Code session transcripts to debug plugins, understand context usage, and trace execution flow |
Transcript Analyzer Skill
Use this skill when you need to analyze Claude Code session transcripts for:
- Debugging plugin behavior
- Understanding context/token usage patterns
- Tracing tool execution flow
- Finding sources of context bloat
- Investigating errors or unexpected behavior
Transcript Location
Claude Code stores session transcripts at:
~/.claude/projects/-PATH-TO-PROJECT/*.jsonl
The path uses dashes instead of slashes. For example:
- Project:
/Users/bfreis/dev/myproject - Transcripts:
~/.claude/projects/-Users-bfreis-dev-myproject/*.jsonl
Transcript Structure
Transcripts use JSONL format (one JSON object per line).
Record Types
| Type | Description |
|---|---|
summary |
Session metadata |
file-history-snapshot |
File change tracking |
user |
User messages (includes tool results) |
assistant |
Assistant messages (includes tool calls) |
Message Structure
{
"type": "user" | "assistant",
"uuid": "message-uuid",
"parentUuid": "parent-message-uuid",
"sessionId": "session-uuid",
"isSidechain": false,
"timestamp": "2025-12-06T...",
"message": {
"role": "user" | "assistant",
"content": [...],
"usage": { "input_tokens": N, "output_tokens": N }
}
}
Content Block Types
In assistant messages:
text- Regular text responsetool_use- Tool invocation with.id,.name,.inputthinking- Extended thinking blocks
In user messages:
text- User inputtool_result- Tool output with.tool_use_id,.content
Critical Gotchas
- Content is ALWAYS an array - Even single text blocks
- Tool result
.contentcan be string OR array - Handle both:if .content | type == "array" then (.content | map(.text // "") | add) else .content end - Use
[]?not[]- Handles missing fields gracefully - Sub-agents have
isSidechain: true- Filter these for main conversation only parentUuidlinks threads - Not line order
Using transcript-tool
The skill provides a CLI at scripts/transcript-tool:
# Quick overview
transcript-tool summary session.jsonl
# Find context bloat sources
transcript-tool bloat session.jsonl 15
# Tool usage breakdown
transcript-tool tools session.jsonl
# Trace specific tool
transcript-tool trace-tool session.jsonl Read
# Find errors
transcript-tool errors session.jsonl
# Custom jq query
transcript-tool extract session.jsonl '.type'
Common Analysis Workflows
1. Debug Plugin Behavior
# Find skill invocations
transcript-tool trace-skill session.jsonl plan-generator
# See what tools were used
transcript-tool tools session.jsonl
# Check for errors
transcript-tool errors session.jsonl
2. Investigate Context Bloat
# Find largest tool results
transcript-tool bloat session.jsonl 20
# Message size analysis
transcript-tool messages session.jsonl
# Identify specific large results
transcript-tool extract session.jsonl '
select(.type == "user") |
.message.content[]? |
select(.type == "tool_result") |
select((.content | tostring | length) > 10000) |
.tool_use_id
'
3. Trace Execution Flow
# All tool calls in order
transcript-tool extract session.jsonl '
select(.type == "assistant") |
.message.content[]? |
select(.type == "tool_use") |
"\(.name): \(.input | keys | join(", "))"
'
Raw jq Recipes
For complex analysis, use jq directly:
Count content block types:
jq -r '
select(.type == "user" or .type == "assistant") |
.message.content[]? |
.type
' session.jsonl | sort | uniq -c
Find tool call by ID:
jq -r --arg id "toolu_xxx" '
select(.type == "assistant") |
.message.content[]? |
select(.type == "tool_use" and .id == $id)
' session.jsonl
Get corresponding tool result:
jq -r --arg id "toolu_xxx" '
select(.type == "user") |
.message.content[]? |
select(.type == "tool_result" and .tool_use_id == $id) |
.content
' session.jsonl
Token usage per message:
jq -r '
select(.type == "assistant" and .message.usage) |
"\(.message.usage.input_tokens) in, \(.message.usage.output_tokens) out"
' session.jsonl