This implementation analysis is limited to the public mirror snapshot dated March 31, 2026. The mirror is not an official Anthropic source repository and does not establish behavior in later product releases. Source links are pinned to that commit; official documentation supplies product concepts, while feature gates and missing modules are identified separately.
The same session exists in several forms. The screen shows messages and progress. The transcript persists message relationships and recovery records. The client holds active session state. The provider receives a validated request assembled for the next turn. Resume has to reconnect all four.
Reading goal: follow a session from transcript writing through cleanup and deserialization, then see which non-message state is restored before the next query can run correctly.
transcript rows
-> loadConversationForResume(sessionId)
-> clean incomplete tail / tool_use pairs
-> run SessionStart resume hooks
-> processResumedConversation()
-> restore AppState, session pointer, worktree, agent, replacement state
1. The screen, transcript, runtime, and API request differ
The UI may collapse tool output or hide progress records because its job is to present the current interaction. The transcript stores message ids, parent ids, sidechain entries, metadata, file-history snapshots, content replacements, and compaction records. Active runtime state adds the selected session, worktree, agent, caches, and tool-related state. Finally, the API request contains only provider-valid messages and tool schemas.
| Representation | Responsibility | Recovery risk |
|---|---|---|
| UI | Messages, progress and input state. | A restored screen can hide incomplete runtime restoration. |
| Transcript | Message chain and persisted auxiliary entries. | Duplicate messages, unresolved tails and incorrect parent links. |
| Runtime | Session id, worktree, agent, history and replacements. | Records may refer to the wrong session or working directory. |
| API request | Provider-valid messages and tool schemas. | Unpaired tools, orphaned thinking or oversized outputs. |
2. Transcript writing preserves message relationships
useLogMessages() observes the current messages. Because normal updates append to the list, it usually sends only the new tail to recordTranscript(). If the first message changes after compaction or /clear, the recorder performs full deduplication and recalculates parent relationships.
recordTranscript() cleans messages for logging, checks which ids already exist, and decides which existing message is the parent of a newly written row. It treats compaction carefully: a new compact marker and summary at the front must not make older retained messages appear to be the new parent.
The same storage module writes records that are not ordinary chat messages, including sidechain activity, queue operations, file-history and attribution snapshots, and content replacements. When the query loop replaces an oversized tool result to fit its budget, it persists that replacement. A resumed session can then keep using the reduced result instead of unexpectedly sending the full output again.
3. Loading repairs interrupted conversations
loadConversationForResume() can find the latest session for --continue, resolve a session id for --resume, or accept an already loaded log or transcript file. For a LogOption, it copies the plan, starts copying file history, and checks consistency. A direct JSONL path instead obtains messages and a session id through loadMessagesFromJsonlPath(); this function returns auxiliary records through log?.…, so the file-path branch does not establish the same metadata, replacement, or file-history restoration as the log branch. When background-session support is enabled and live-session discovery succeeds, --continue also skips running non-interactive sessions.
deserializeMessagesWithInterruptDetection() does more than JSON.parse. It migrates legacy attachment types, removes invalid permission modes, and filters unresolved tool calls, thinking-only assistant messages, and empty assistant text. If a turn was interrupted, it adds a meta user message asking the model to continue. If the final valid message is from the user, it inserts a synthetic assistant sentinel so the recovered conversation has a valid sequence. This repairs protocol structure, not an interrupted process stack, and deserialization does not re-execute tools. If Bash changed a file before its tool result was persisted, removing the unresolved call does not undo that file change: the next turn must inspect actual files and task status. Before deserialization, the loader also restores Skill state from invoked_skills attachments so it can survive further compaction.
After loading messages, the resume path runs SessionStart hooks with source: resume and appends their messages. Resume is a new execution starting point, so session-level hooks get another chance to supply required context.
4. processResumedConversation restores active session state
CLI --continue and the different --resume sources converge on processResumedConversation(). For a normal resume, it switches to the old session id and handles cross-directory transcript paths. It restores cost state, metadata, and worktree information, then passes agent state and loaded replacement records into the next runtime. The context-collapse restore call is conditional on CONTEXT_COLLAPSE; the invoked module is absent from this snapshot. The visible call proves only that commit entries and a staged snapshot are its inputs, not how the missing implementation replays them or that every session enables it.
adoptResumedSessionFile() makes the original transcript the write target. This matters even if the user exits before sending another message: cleanup still needs to save updated metadata such as the session name, tags, or agent. Without adoption, those updates could remain only in memory.
5. Interactive /resume performs a live session switch
REPL.resume() runs inside an already active interface. It first deserializes the target. Before switching sessions, it executes SessionEnd hooks for the current session, then runs the target's SessionStart(resume) hooks, restores file history, attribution, agent state, read-file state, metadata, worktree, remote tasks, and content replacements, and finally replaces the visible messages.
The resume picker is only the selection interface. Its loader still checks cross-project sessions, restores cost and agent data, conditionally invokes the context-collapse restore interface, and passes initial messages, file-history snapshots, and replacement records to the REPL. The session list progressively enriches lightweight log entries so it can filter and present sessions that are actually resumable.
6. Fork resume must write a new transcript
A normal resume continues the original session: it reuses the session id and adopts the existing transcript. --fork-session derives a new session instead. It keeps the fresh session id, does not adopt the original file, and writes messages through recordTranscript() into a new transcript. Metadata restoration removes worktreeSession, and the fork does not restore or take ownership of the original worktree. Otherwise an exit-time removal could delete a worktree still referenced by the original session. A fork session is not automatically an isolated filesystem; separate worktree isolation is still needed for independent file changes.
Content replacements need special handling in a fork. The replacement entries are written into the new session so its tool-use ids can still resolve to the reduced results. Otherwise the fork could restore an oversized tool output that the original session had already replaced.
| Path | Session id | Write target | Reason |
|---|---|---|---|
| Normal --continue / --resume | Reuse the restored id. | Adopt the original transcript. | New records continue the original chain. |
| Interactive /resume | Switch to the selected id. | Adopt the selected transcript. | End the current session before installing the target runtime. |
| --fork-session | Keep the fresh id. | Write a new transcript. | Derived messages must not modify the original history. |
| Fork with replacements | Keep the fresh id. | Seed replacement entries in the new session. | Retain reduced tool outputs when the fork is resumed again. |
Resume is therefore state reconstruction, not chat replay. Messages are only one input alongside file history, content replacements, compact records, metadata, agent selection, worktree state, hooks, and the transcript chosen for future writes. Which auxiliary state is available depends on the loading source, persisted records, and feature gates.
Sources
The source-code claims in this article are based on the public mirror and the linked official documentation. Server-side session storage is not inferred from client-side recovery code.