[BUG] "Fork Conversation from here" doesn't work

Status Open
Reported on v2.1.84
Maintainer reply None cached
Activity 16 comments · opened Mar 26, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

"Fork Conversation from here" option doesn't work with

  • Visual Studio Code : Version: 1.107.1 (Universal)
  • Claude Code for VS Code : Version 2.1.84

Rest of the functionality is working fine.

What Should Happen?

"Fork Conversation from here" option should ideally open another pane with appropriate chat history forked at the right place.

Error Messages/Logs

No Error logs seen in the UI

Steps to Reproduce

Claude Code for VS Code extension with above mentioned versions and try "Fork Conversation from here" option.

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.84

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

VS Code integrated terminal

Additional Information

_No response_

View original on GitHub ↗

16 Comments

github-actions[bot] · 5 months ago

Found 2 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/36435
  2. https://github.com/anthropics/claude-code/issues/22016

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

ishantyagi · 5 months ago

Possible duplicate issues listed above also got closed without fix.

qwertie · 4 months ago

This has been broken for at least 2½ months.

KernelBypass · 3 months ago
This has been broken for at least 2½ months.

Longer than that.
Should not be hard to fix either. A real bummer.

KernelBypass · 3 months ago
Possible duplicate issues listed above also got closed without fix.

Here's another dupe:
https://github.com/anthropics/claude-code/issues/25085

Blackspell01 · 3 months ago

this is fucking ridiculous

qwertie · 3 months ago

It has been working for me for at least a couple of weeks, but I guess that's not true for everyone?

KernelBypass · 3 months ago
It has been working for me for at least a couple of weeks, but I guess that's not true for everyone?

Does forking the convo chat actually fork the context (previous messages) for you?

That's how Codex and GHCP harnesses work for me, but not Claude Code, and it's very frustrating.
/copy doesn't work properly either - only copies one line of the message, not the entire message, and doesn't work at all in the VS Extension (not even present in the slash commands), even though the extension is just the CLI in a wrapper AFAIK.

Anthropic makes great LLM models, but very frustrating harnesses, especially the VS Code extension, and they just let users reports go stale and auto-close them despite lots of community vote behind ignored issues.

qwertie · 3 months ago

Yeah, Claude Code must be the worst harness, but when I ask to fork a conversation, it includes all the history minus the message I selected for forking... or a little earlier. Today I tested it and it subtracted both the message I forked from and the message before that one.

jfmmm · 3 months ago

It's working half the time, that's the issue. It's also never obvious why it doesn't work. It's so annoying.

Been happening since forever too.

omril1 · 3 months ago

From my experience, the feature has two modes:

  1. Not working when you click on it
  2. Working, but the new forked session gets lost when you restart vscode
tommedema · 2 months ago

has not been working for me for weeks/months too

Nillth · 2 months ago

Still reproducing on Linux, Claude Code extension v2.1.183 (linux-x64), VS Code 1.125.0 — so this isn't macOS-only and isn't fixed as of 2.1.183.

One additional trigger condition worth noting: I hit this when forking mid-turn, i.e. invoking "Fork Conversation from here" while the assistant is still generating its response. The new pane opens and the last message text is populated, but the conversation history isn't carried into it.

This lines up with the root cause in #63831: the spawned process starts with resume: undefined because the resumeSessionAt message ID from the fork_conversation request isn't passed through — so the fork point is lost and the new session starts without history.

StrikerTheHedgefox · 2 months ago

Can reproduce, on windows. Just freezes any time I try to fork the convo.

dekan-aleksandr · 1 month ago

Root cause for the silent-fork failure, traced through the bundle on 2.1.2182.1.220 (linux-x64). The fork itself works; the result is thrown away immediately afterwards.

Chain

  1. fork_conversation succeeds. A complete transcript is written to <projectRoot>/<new-uuid>.jsonl and file-history backups are hard-linked. In my case: 9,303 valid JSONL lines, 47.8 MB, zero parse errors.
  2. The new tab opens with data-initial-session=<new id> and calls activateSessionFromServer(initialSession, initialPrompt).
  3. That resolves the id against list_sessions:

``js
let r = (await n.listSessions()).sessions.find(l => l.id === e);
if (!r) return false;
``

  1. list_sessionsLItPItcfeafe. afe reads only the first and last 64 KB (jd = 65536) of each .jsonl and must derive a label from one of: customTitle / aiTitle (head or tail), lastPrompt / summary (tail), or a first user message (head, via tIt). Otherwise:

``js
let f = l || Rc(i,"lastPrompt") || Rc(i,"summary") || u;
if (!f) return null; // session omitted from the list entirely
``

  1. A forked transcript satisfies none of them:
  • its first user record is the compact-summary ("isCompactSummary": true, "This session is being continued from a previous conversation…"), which tIt skips by design;
  • the rest of the first 64 KB is attachment records — no other user message;
  • fork copies only transcript messages, so no last-prompt / summary / custom-title record is carried over (source session: 935 last-prompt records; fork: 0).
  1. afe returns null → the session is not in list_sessionsactivateSessionFromServer returns false → the webview falls back to createSession({isExplicit:false}) with only the prompt prefilled.

Observable signature

The forked tab spawns without a resume argument — that is the tell, and it matches #31831 exactly:

Received message from webview: {"type":"launch_claude","channelId":"…","cwd":"…","permissionMode":"…"}
                                                            ^ no "resume" field

A normal resume carries "resume":"<session-id>".

This is not a mid-generation race. The same resume: undefined appears on forks taken while idle, and the missing-label mechanism also explains a symptom a race cannot: on my machine 13 unrelated large sessions (18–48 MB) were simultaneously invisible in the session picker, for exactly the same reason. Waiting for generation to finish is not a reliable workaround.

Trigger condition

Forking any session whose transcript begins with a compact-summary — i.e. any conversation that has been auto-compacted at least once. Short sessions fork fine, which is why this looks intermittent.

Verification

Replaying afe()'s logic over 48 session files reproduced the exact split: the 3 forked sessions plus 10 other large ones yielded no label; the other 35 did.

Fix

A last-resort label makes afe total, so forked sessions always resolve:

// before
let f = l || Rc(i,"lastPrompt") || Rc(i,"summary") || u;
// after
let f = l || Rc(i,"lastPrompt") || Rc(i,"summary") || u || ("Session " + e.slice(0, 8));

Patching that one expression in extension.js fixed forking here immediately. A cleaner upstream fix would be to have forkSession write a custom-title (or copy the source's lastPrompt) into the new transcript, since ensureSessionLoaded already understands {"type":"custom-title", "sessionId":…, "customTitle":…}.

Recovering sessions already lost to this

Appending that record — the same one "Rename session" writes — makes an invisible session listable again without touching existing data:

{"type":"custom-title","sessionId":"<uuid>","customTitle":"<any label>"}

Separate bug, same button

Forking from a message inside a Task/subagent block fails differently, with a visible error:

Failed to fork conversation: Message <uuid> not found in session <uuid>

Subagent messages live in <projectRoot>/<sessionId>/subagents/agent-*.jsonl with "isSidechain": true, but ensureSessionLoaded reads only <projectRoot>/<sessionId>.jsonl, so forkSession throws. Workaround: fork from a main-thread message.

dekan-aleksandr · 1 month ago

Root cause for the silent-fork failure, traced through the bundle on 2.1.2182.1.220 (linux-x64). The fork itself works; the result is thrown away immediately afterwards.

Chain

  1. fork_conversation succeeds. A complete transcript is written to <projectRoot>/<new-uuid>.jsonl and file-history backups are hard-linked. In my case: 9,303 valid JSONL lines, 47.8 MB, zero parse errors.
  2. The new tab opens with data-initial-session=<new id> and calls activateSessionFromServer(initialSession, initialPrompt).
  3. That resolves the id against list_sessions:

``js
let r = (await n.listSessions()).sessions.find(l => l.id === e);
if (!r) return false;
``

  1. list_sessionsLItPItcfeafe. afe reads only the first and last 64 KB (jd = 65536) of each .jsonl and must derive a label from one of: customTitle / aiTitle (head or tail), lastPrompt / summary (tail), or a first user message (head, via tIt). Otherwise:

``js
let f = l || Rc(i,"lastPrompt") || Rc(i,"summary") || u;
if (!f) return null; // session omitted from the list entirely
``

  1. A forked transcript satisfies none of them:
  • its first user record is the compact-summary ("isCompactSummary": true, "This session is being continued from a previous conversation…"), which tIt skips by design;
  • the rest of the first 64 KB is attachment records — no other user message;
  • fork copies only transcript messages, so no last-prompt / summary / custom-title record is carried over (source session: 935 last-prompt records; fork: 0).
  1. afe returns null → the session is not in list_sessionsactivateSessionFromServer returns false → the webview falls back to createSession({isExplicit:false}) with only the prompt prefilled.

Observable signature

The forked tab spawns without a resume argument — that is the tell, and it matches #31831 exactly:

Received message from webview: {"type":"launch_claude","channelId":"…","cwd":"…","permissionMode":"…"}
                                                            ^ no "resume" field

A normal resume carries "resume":"<session-id>".

This is not a mid-generation race. The same resume: undefined appears on forks taken while idle, and the missing-label mechanism also explains a symptom a race cannot: on my machine 13 unrelated large sessions (18–48 MB) were simultaneously invisible in the session picker, for exactly the same reason. Waiting for generation to finish is not a reliable workaround.

Trigger condition

Forking any session whose transcript begins with a compact-summary — i.e. any conversation that has been auto-compacted at least once. Short sessions fork fine, which is why this looks intermittent.

Verification

Replaying afe()'s logic over 48 session files reproduced the exact split: the 3 forked sessions plus 10 other large ones yielded no label; the other 35 did.

Fix

A last-resort label makes afe total, so forked sessions always resolve:

// before
let f = l || Rc(i,"lastPrompt") || Rc(i,"summary") || u;
// after
let f = l || Rc(i,"lastPrompt") || Rc(i,"summary") || u || ("Session " + e.slice(0, 8));

Patching that one expression in extension.js fixed forking here immediately. A cleaner upstream fix would be to have forkSession write a custom-title (or copy the source's lastPrompt) into the new transcript, since ensureSessionLoaded already understands {"type":"custom-title", "sessionId":…, "customTitle":…}.

Recovering sessions already lost to this

Appending that record — the same one "Rename session" writes — makes an invisible session listable again without touching existing data:

{"type":"custom-title","sessionId":"<uuid>","customTitle":"<any label>"}

Separate bug, same button

Forking from a message inside a Task/subagent block fails differently, with a visible error:

Failed to fork conversation: Message <uuid> not found in session <uuid>

Subagent messages live in <projectRoot>/<sessionId>/subagents/agent-*.jsonl with "isSidechain": true, but ensureSessionLoaded reads only <projectRoot>/<sessionId>.jsonl, so forkSession throws. Workaround: fork from a main-thread message.