跪拜 Guibai
← All articles
Architecture · AI Programming · Backend

How Claude Code Rebuilds a Session from a JSONL Log

By candyTong ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Understanding that Claude Code's session recovery is log replay, not process snapshot, explains why terminal history and model context can diverge — and gives developers a concrete debugging path when the model seems to forget earlier conversation.

Summary

Session recovery in Claude Code is a two-phase process: first, it reconstructs a resumable conversation from a local JSONL file; only after the user types a new message does it assemble the model context. The JSONL stores every message, tool call, and state change as append-only JSON records, linked by uuid and parentUuid fields. Rewind operations create fork points without deleting old messages — new messages simply append to the file and point back to an earlier parent, leaving multiple conversation paths in the same log.

Compaction boundaries and summaries are also written into the JSONL. When a session is compacted, a boundary record with a null parentUuid cuts off the old chain, and a summary message becomes the new head. Recovery then walks forward from the boundary, never re-reading the original messages that were compacted. Unfinished turns — a tool call without a result, or a user message without a reply — get cleaned up into a structurally valid conversation without re-executing any tools.

Work state like agent configuration, Git worktree paths, file read caches, and file-history snapshots are stored alongside messages in the same JSONL. During recovery, Claude Code reads these records by type and writes them back into the running process. The result is a restored environment, not a new state file. The terminal shows the full conversation path, but the model may receive less: context assembly starts from the last compaction point and may trim or summarize further before the next request.

Takeaways
Claude Code saves each session as a single append-only JSONL file under ~/.claude/projects/<project>/<session-id>.jsonl.
Every record has a top-level type field that tells the recovery logic how to interpret it; user and assistant records also carry a message.role for model interaction direction.
Messages are linked by uuid and parentUuid, forming a chain that can contain multiple forks from /rewind operations without deleting old messages.
Compaction inserts a boundary record with parentUuid: null and a summary message; recovery starts from the boundary and never re-reads the original compacted messages.
Unfinished turns are cleaned up structurally: pending tool calls without results are removed, and user messages without replies get placeholder messages to maintain question-answer pairing.
Work state — agent settings, worktree paths, file read caches, file-history snapshots — is stored in the same JSONL and restored by type into the running process.
Recovery is conditional: if a saved agent no longer exists or a worktree directory is gone, Claude Code falls back to current settings and directory.
The terminal displays the full reconstructed conversation path, but the model context is assembled later from the last compaction point and may be further trimmed or summarized.
Conclusions

JSONL as a state-recovery format is underappreciated: it's human-readable enough to debug, structured enough for a program to replay, and append-only by nature, so crashes rarely corrupt earlier records.

The separation between terminal display and model context is a deliberate design choice, not a bug. It means developers can scroll through full history while the model works with a compressed subset, but it also creates a debugging trap when the model misses details visible on screen.

Storing worktree state and agent configuration in the same log as conversation messages turns the JSONL into a portable session file — move the file and the project directory, and the session can theoretically resume on another machine.

Rewind without deletion is a clever UX pattern: it preserves the full decision tree in the log, so users can revisit abandoned paths later, but it also means JSONL files grow monotonically and may need occasional cleanup.

Concepts & terms
JSONL (JSON Lines)
A text format where each line is a complete, independent JSON object. Programs can append new records to the end of a file without rewriting the whole thing, making it suitable for streaming logs and session persistence.
parentUuid chain
A linked-list structure within Claude Code's session log where each message record points to its logical predecessor via a parentUuid field. Recovery walks this chain backward from the last message, then reverses it to reconstruct the conversation order.
Compaction boundary
A special system record inserted into the JSONL when earlier messages are summarized. It carries parentUuid: null to sever the old chain and a logicalParentUuid for bookkeeping, plus metadata about whether compaction was automatic or manual.
Worktree (Git)
A Git feature that lets you check out multiple branches of the same repository into separate directories simultaneously. Claude Code stores the active worktree path in session logs so it can re-enter the correct isolated directory on resume.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗