Forking a conversation with a large first message creates an unreachable session and opens a blank tab

Status Open
Reported on v2.1.251
Maintainer reply None cached
Activity 0 comments · opened Aug 31, 2026

Forking a conversation whose first message is larger than the 64 KB head window produces a session that is written to disk but is unreachable from the UI. The new tab silently opens as a blank "Untitled" conversation with the clicked prompt preloaded, and the fork never appears in the session list. The transcript itself is complete and correct — only the UI cannot find it.

Reproduced on demand; not intermittent once the trigger is understood.

Environment

  • Extension: anthropic.claude-code 2.1.251 (darwin-arm64)
  • VS Code 1.135.0, macOS 26.5.2, arm64

Reproduction

  1. Start a conversation whose first user message contains an image or a large paste, so that the first user line in the transcript exceeds 64 KB. In my case the line was 679,350 bytes of a 765 KB file.
  2. Have a normal exchange.
  3. Hover any later user message and choose Fork conversation from here.

Observed: a new tab opens titled "Untitled" showing the new-session splash, with the clicked message's text sitting in the composer. The forked session does not appear in the session list, then or after a reload.

Expected: the new tab shows the forked conversation.

The fork is written. A new .jsonl appears in the project directory, with an unbroken parent chain and, in my case, 21 renderable messages. It is simply not reachable.

Root cause

Three things combine. Any one of them being less brittle would prevent the failure.

1. The fork writer emits no naming metadata. forkSession copies the transcript faithfully but writes no custom-title, ai-title or last-prompt entry, and writes summary only when the parent session already had one. So a fork typically has none of the four.

2. The session list builder then falls back to parsing the head, and gives up. Each list entry is built from only the first and last 64 KB of the file (fQ = 65536):

let N = U || w3(tail,"lastPrompt") || w3(tail,"summary") || H;  // H = first prompt from the head
if (!N) return null;                                             // dropped from the list

When the first user line does not both start and end inside that 64 KB head, H is undefined. Combined with (1), N is undefined and the session is dropped from the list entirely.

3. Opening the fork resolves it only through that list. The new tab's bootstrap:

z.listSessions("panel_boot").then(() => {
  if (F.initialSession) {
    z.activateSessionFromServer(F.initialSession, F.initialPrompt).then((O) => {
      if (!O) {
        z.createSession({isExplicit:false}).then((R) => {
          if (R && F.initialPrompt) R.initialPrompt.value = F.initialPrompt;
        })
      }
    })

and activateSessionFromServer never opens the transcript by id, even though it has the id:

let Z = (await X.listSessions("activate")).sessions.find(z => z.id === $);
if (!Z) return false;

A lookup miss therefore becomes a silently created blank session rather than an error. This is the step that turns a cosmetic listing problem into apparent data loss.

Note the asymmetry: elsewhere the loader locates a transcript by direct stat on <projectDir>/<sessionId>.jsonl, which works fine for these files. Only the activation path insists on the list.

Impact

The user's reasonable conclusion is that forking silently discarded the conversation. In practice the branch is stranded: no list entry, no error, and no indication that a file was written.

Suggested fixes

Any single one of these closes the hole. The third is the most valuable.

  1. Have forkSession write a custom-title (or copy the parent's title) into the new transcript. Smallest change.
  2. When the head window yields no parseable user line, fall back to scanning further into the file rather than returning null.
  3. Make activateSessionFromServer fall back to loading the transcript by id when the list lookup misses. The id is already in hand and the direct-stat loader already exists. At minimum, surface an error instead of silently creating a blank session.

Workaround

The transcript is intact, so an orphaned fork can be recovered by giving it a name. Append one line to the fork's .jsonl, matching what the rename path writes:

{"type":"custom-title","sessionId":"<id>","customTitle":"my fork"}

Reload the window and the session appears with its full history. Resuming from the CLI also works, since that path locates the file directly:

claude --resume <id>

Possibly related

#69272 concerns fork anchoring in the same menu, but is a separate problem.

View original on GitHub ↗