Session Format
Claude Code writes session transcripts as JSONL (one JSON object per line) to ~/.claude/projects/. Each line has a consistent top-level structure that the parser decodes into structured Event values.
JSONL Line Structure
Every line in a transcript file follows this schema:
{
"type": "assistant" | "user",
"timestamp": "2026-02-19T14:30:00.000Z",
"sessionId": "abc123...",
"message": { ... }
}
The message field contains a role and an array of content blocks. The parser handles two entry types:
assistantentries containtextblocks (Claude's prose) andtool_useblocks (tool invocations)userentries containtextblocks (human messages) andtool_resultblocks (tool outputs)
Event Types
The parser produces four event types:
| Type | Source | Description |
|---|---|---|
tool_use |
assistant + user | A tool call paired with its result |
user |
user text block | A human message |
assistant |
assistant text block | Claude's reasoning or response |
error |
tool_result with is_error: true |
A failed tool invocation |
Parsing Pipeline
ParseTranscript processes the JSONL file in a single pass:
- Scan each line into a
rawEntrystruct - For assistant entries, extract
tool_useblocks and store them in a pending map keyed by tool ID - For user entries, match
tool_resultblocks against pending tool uses bytool_use_id - Pair the tool invocation with its result to compute duration and success/failure
- Extract text blocks as
userorassistantevents
sess, err := session.ParseTranscript("/path/to/session.jsonl")
if err != nil {
log.Fatal(err)
}
for _, evt := range sess.Events {
if evt.Type == "tool_use" && !evt.Success {
fmt.Printf("FAILED: %s %s -- %s\n", evt.Tool, evt.Input, evt.ErrorMsg)
}
}
Tool Input Extraction
Each tool type has its input decoded differently:
| Tool | Extracted Input |
|---|---|
Bash |
Command string (with optional # description suffix) |
Read |
File path |
Edit |
File path with (edit) suffix |
Write |
File path with byte count |
Grep |
/pattern/ in path |
Glob |
Glob pattern |
Task |
[subagent_type] description |
Unknown tools fall back to listing the JSON keys from the input object.
Listing Sessions
ListSessions scans a directory for .jsonl files and performs a fast two-pass read (first and last timestamp) without fully parsing every event:
sessions, err := session.ListSessions("~/.claude/projects/")
if err != nil {
log.Fatal(err)
}
for _, s := range sessions {
fmt.Printf("%s %s %s\n", s.ID[:8], s.StartTime.Format("02 Jan 15:04"), s.EndTime.Sub(s.StartTime))
}
Results are sorted newest first.
Cross-Session Search
Search parses all sessions and finds tool_use events matching a case-insensitive query against both input and output text:
results, err := session.Search("~/.claude/projects/", "migration")
for _, r := range results {
fmt.Printf("[%s] %s: %s\n", r.SessionID[:8], r.Tool, r.Match)
}
Returns []SearchResult with session ID, timestamp, tool name, and matching context.