VS Code extension: conversation that entered a git worktree restores as an empty conversation after VS Code restart (restored tab loses its sessionID)
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?
Close VS Code while a conversation that has entered a git worktree is open/working. On relaunch, VS Code restores the conversation tab, but the tab comes back as a brand-new empty conversation — no history, generic "Claude Code" title, no error. The original transcript is intact on disk the whole time, in the worktree-keyed project directory. Conversations that stayed in the main checkout restore fine in the same relaunch.
The failure is in the extension's tab-restore path, not the CLI resume path: the persisted webview state for the affected tab has lost its sessionID, so on restore the webview sends launch_claude with no resume parameter and the extension spawns a fresh session. (The CLI itself resumes worktree sessions by ID without problems — a third worktree conversation in the same relaunch was resumed by ID and recovered fully, see "Counter-example" below.)
Because the workspace's "Past conversations" list is keyed to the workspace folder's project dir, the orphaned worktree conversation doesn't appear there either (see #34437), so from inside the UI the conversation looks permanently lost.
Evidence (from an occurrence on 2026-07-30 → 2026-07-31)
Three conversations were in git worktrees when VS Code was closed at ~00:07 local:
| Conversation (worktree) | Session ID | Outcome on relaunch |
|---|---|---|
| contracts-section-spec | 9719e32b-… | restored empty (no resume sent) |
| iot-commands-arm-signatures | 3acda470-… | restored empty (no resume sent) |
| plan200-wave5 | e8529909-… | resumed correctly by ID |
All main-checkout conversations (6+) resumed correctly.
1. Transcripts intact on disk — the "lost" conversations still exist, sizeable, last written at VS Code close time (00:07):
~/.claude/projects/…-Milbank-Platform--claude-worktrees-contracts-section-spec/
9719e32b-….jsonl 432 KB mtime Jul 31 00:07 (ends with a "last-prompt" record)
~/.claude/projects/…-Milbank-Platform--claude-worktrees-iot-commands-arm-signatures/
3acda470-….jsonl 1.7 MB mtime Jul 31 00:07
No duplicate/empty .jsonl with the same IDs was created anywhere — this is purely a lost-reference bug, not transcript corruption.
2. Healthy tabs persist their session ID in the workspace state.vscdb (webview serializer state):
"extensionId":"Anthropic.claude-code","state":"{\"isFullEditor\":false,\"sessionID\":\"fb8463b2-…\"}"
Grepping the workspace's state.vscdb finds the session IDs of every conversation that restored correctly — and zero occurrences of 9719e32b or 3acda470. The affected tabs' persisted webview state had lost the sessionID before/at shutdown.
3. Extension log on relaunch (…/logs/20260731T065003/window1/exthost/Anthropic.claude-code/Claude VSCode.log) — restored tabs that kept their state relaunch with resume:
06:50:11 Received message from webview: {"type":"launch_claude","channelId":"…","cwd":"c:\\…\\Milbank Platform","resume":"bbcdaab7-…", …}
06:52:10 … "resume":"d82f6bc4-…" …
06:55:14 … "resume":"e8529909-…" … ← worktree session, resumed fine
…while exactly two launches (matching the two lost worktree conversations) arrive with no resume at all, preceded by rename_tab back to the generic default title:
06:51:46 {"type":"request", …"type":"rename_tab","title":"Claude Code", …}
06:51:46 {"type":"launch_claude","channelId":"1x24b2djycnj","cwd":"c:\\…\\Milbank Platform","permissionMode":"auto","thinkingLevel":"default_on"} ← no resume
06:51:59 {"type":"launch_claude","channelId":"mkw1sj9lblj", …} ← no resume
Neither lost session ID appears anywhere in the relaunch log as a resume target.
Counter-example / narrowing observation
Being in a worktree does not break restore by itself: e8529909 (worktree plan200-wave5, a 20 MB transcript dating from Jul 15 that has survived many restarts) resumed correctly in the same relaunch. The observable difference: the two conversations that lost their tab state had been idle for ~30–60 minutes before VS Code was closed (last transcript activity 23:07 / 23:34 local; close at 00:07), while the surviving worktree conversation was active in the final minutes before close. This suggests the webview's persisted state (or the extension's session bookkeeping for the tab) is cleared or overwritten at some point after the session goes idle in a worktree, so whatever is on disk at shutdown no longer contains the sessionID.
What Should Happen?
Any of:
- The restored tab keeps its
sessionIDand resumes the conversation (the CLI-side resume demonstrably works, even across worktree-keyed project dirs); or - If the tab state is unusable, the extension recovers the mapping some other way (e.g. its own tab→session store, or matching live/most-recent sessions for the workspace incl. worktree-keyed project dirs); or
- At minimum, the tab restores to a "couldn't restore this conversation — find it in past conversations" state instead of silently opening a brand-new empty conversation, and worktree-session transcripts are findable from the workspace's Past Conversations list.
Actual
Tab silently restores as a fresh empty conversation. The original conversation is invisible in the workspace's Past Conversations (worktree-keyed project dir), so to the user it reads as total loss of a long-running task's history.
Error Messages/Logs
Steps to Reproduce
- In the VS Code extension, open a conversation in a git repo workspace and have it
EnterWorktree(transcript relocates to~/.claude/projects/<munged-repo>--claude-worktrees-<name>/). - Let the conversation go idle for a while (the failures correlate with ≥ ~30 min idle; a busy-until-close worktree conversation survived).
- Close the VS Code window; reopen the same workspace.
- The restored tab shows an empty "Claude Code" conversation; extension log shows
launch_claudewithoutresume; workspacestate.vscdbcontains no webview state with thatsessionID.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
v2.1.220
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
VS Code integrated terminal
Additional Information
_No response_
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Reproduced on macOS, v2.1.221 (original report: Windows, v2.1.220). Workspace is a repo root with 35 git worktrees under
.claude/worktrees/.Restore burst
launch_claudeevents from one window'sClaude VSCode.log, restore burst 13:03:36 – 13:17:56:The two carrying a resume target:
All 10 events had
"cwd":"<repo>"— the repo root. None carried a worktree cwd, including for tabs that had been running in worktrees.Session IDs are still present in
state.vscdbThe original report found zero occurrences of the lost session IDs in the workspace
state.vscdb. Here, most are still present. Grepping the workspacestate.vscdbfor webview serializer state finds entries for six worktree conversations, none of which appears as aresumetarget anywhere in the relaunch log:Two further affected conversations,
19b517a5and44733947, have zero occurrences. So both states — ID retained, and ID absent — occur in the same relaunch.Transcripts intact
Nothing is lost on disk. The affected transcripts are present in their worktree-keyed project dirs, 1.6 MB – 7.4 MB each, last written at window-close time.
One observation that may be a useful lead.
208b4bfcis the only resumed session whose real transcript lives in a worktree bucket (4.3 MB there), and it is also the only one of these ten sessions with any file under the workspace bucket: a 109-bytecustom-titlerecord. The other nine have no file in the workspace bucket at all. The remaining small files in the workspace bucket are allteleported-fromrecords belonging to unrelated sessions.Recovery
Resuming by ID from the worktree's own cwd recovers the conversation fully, as does File → Open Folder on the worktree — which also makes the workspace session list show it normally.
That points at the practical impact the report already describes: because the session list is keyed to the workspace folder's project dir (#34437), worktree conversations are not reachable from the UI, so an empty restored tab reads as permanent loss rather than as something recoverable.
Follow-up: there is a way to trigger the "empty conversation instead of a resume" symptom deterministically, without a restart. It may be a faster way to exercise the same code path.
Mechanism
The extension registers a URI handler at runtime via
window.registerUriHandler(it is not declared inpackage.json—contributes.uriHandleris absent). One of its routes:primaryEditor.opencallscreatePanel(sessionId, prompt, viewColumn).createPanelaccepts no cwd — the resulting panel inherits the workspace folder of whichever window handles the URI. So if the handling window's workspace does not own the session's project dir, the session id cannot be resolved.Repro
.claude/worktrees/<name>), so its transcript lives in the worktree-keyed project dir.resume:No error is surfaced in the UI.
Control
Same session id, but open the worktree as the workspace folder first, then fire the identical deep link:
Resumes correctly with full history. Same session id, same command — only the handling window's workspace differs.
Scope of the claim
This reproduces the observable symptom from the original report — the extension opening an empty conversation instead of resuming, with
launch_claudecarrying noresume. Whether it shares a root cause with the restart-path failure I have not established; the restart case additionally loses the tab's worktree cwd, which this route never had. Offering it because it needs no restart and no idle period, so it may be useful for bisecting.In both cases the transcript is untouched on disk (byte-identical size and mtime before and after, no stray file created in the workspace's project dir), consistent with this being a lost-reference bug rather than transcript corruption.
The transcript is still on disk here; the empty tab is being launched as a new session because the VS Code restore state has lost the
sessionIDor the worktree context. The two worktree JSONLs being present, pluslaunch_claudearriving without aresumeparameter, separates this from a missing conversation history file.I built
BasedGPT/claude-code-session-recoveryfor related VS Code session-list failures. Runpython tools/diagnose.pyfirst and follow the exact command it prints. If it maps these worktree JSONLs as dropped VS Code entries, run the printed dry run ofrecover_vscode_sessions.pyand keep VS Code fully closed before applying it. As an immediate check, open the affected worktree as the VS Code workspace and resume one of the session IDs directly; that tests whether the transcript and its pointer are still usable.The extension still needs to preserve the tab's
sessionIDand worktree cwd together, or show a recovery message instead of silently opening a fresh session.Hope this helps, if my tools are able to help you, would appreciate a ⭐ :)