[Windows] /resume / session history always empty for projects on mapped network drives — extension and CLI compute different project slugs
Environment
- Claude Code VSCode extension: v2.1.205 (native binary
claude.exe2.1.205, win32-x64) - OS: Windows 11 Pro (10.0.26200)
- Project location: mapped SMB network drive (
Y:→\\server\share), workspace opened asY:\my-project
Summary
When a project lives on a mapped network drive, the VSCode panel's session history (/resume, past chats) is always empty — only the live session appears — even though all transcripts exist and are perfectly valid. Users understandably conclude their history is lost. It is not: the extension is simply looking in the wrong directory.
Root cause
The CLI and the extension canonicalize the project path differently before slugifying it into ~/.claude/projects/<slug>:
- The CLI writes transcripts under the slug of the raw cwd:
y:\my-project → ~/.claude/projects/y--my-project/ ✅ (all sessions are here)
- The extension's session listing first resolves the cwd with
fs.promises.realpath(cwd). In Node,fs.promises.realpathis the native implementation (libuvuv_fs_realpath→GetFinalPathNameByHandleon Windows), which resolves mapped drives to their UNC target — unlikefs.realpathSync/fs.realpath(JS implementation), which keeps the drive letter:
````
> fs.realpathSync('y:/my-project') // JS impl
y:\my-project
> await fs.promises.realpath('y:/my-project') // native impl
\\server\share\my-project
The extension therefore looks for sessions in
~/.claude/projects/--server-share-my-project/ — a directory that does not exist → readdir throws → empty session list.
The mismatch is deterministic: reader and writer can never agree for any workspace on a mapped drive, so history appears permanently empty on every launch.
I verified the rest of the pipeline is healthy by replaying the picker's filtering logic (head/tail 64 KB window, entrypoint/sessionKind programmatic filter, isSidechain first-line check, title extraction) against all 19 transcripts of the affected project: 19/19 pass. Only the directory resolution differs.
Steps to reproduce
- Map a network share:
net use Y: \\server\share - Open
Y:\my-projectin VSCode with the Claude Code extension and run a couple of sessions. - Observe transcripts being created in
%USERPROFILE%\.claude\projects\y--my-project\. - Type
/resume(or open past chats) in the panel.
Expected: past sessions of the workspace are listed.
Actual: the list is empty (only the currently live session is shown). Terminal claude --resume from Y:\my-project works fine, since the CLI uses the drive-letter slug.
Workaround
An NTFS junction making both slugs point at the same directory:
New-Item -ItemType Junction `
-Path "$HOME\.claude\projects\--server-share-my-project" `
-Target "$HOME\.claude\projects\y--my-project"
/resume immediately lists every past session again.
Suggested fix
Use the same canonicalization on the write and read paths. Either avoid the native realpath when deriving the project slug (matching the CLI), or fall back to probing the raw-cwd slug when the realpath-derived slug directory doesn't exist. (Handling both slugs on read would also repair history for users who already have transcripts split across the two forms.)
Showing cached comments. Read the full discussion on GitHub ↗
4 Comments
All 19 sessions are under
y--my-project/. The write path is correct; the extension's native-realpath resolver is reading from the wrong slug, which explains the clean 19/19 pass when you replay the filtering logic against the right directory.This is the split-slug pattern I built
BasedGPT/claude-code-session-recoveryto handle: two slug directories where only one has content, and the reader landing on the empty one. In the toolkit it maps to theone_project_two_session_setscase, though usually triggered by a folder rename or junction rather than a drive-letter to UNC resolution divergence.The junction workaround you found is exactly what I'd recommend. Creating
--server-share-my-projectas a junction targetingy--my-projectgets the extension pointing at the right transcript set without touching the files. Rundiagnose.pyfrom the toolkit first. It maps%APPDATA%\Claude\claude-code-sessions\against~/.claude/projects/and shows both slug directories, so you can confirm the mismatch before creating the junction.The fix you've outlined (fall back to the raw-cwd slug when the realpath-derived directory doesn't exist) is the right upstream call. The second option (handle both slug forms on read) would also repair existing splits for users who already have transcripts under both.
Hope this helps, if my tools are able to help you, would appreciate a ⭐ :)
Confirming this is still present in extension v2.1.210 (Windows 11 Pro 10.0.26200, workspace on a mapped SMB drive). Same root cause verified independently: CLI writes transcripts under the drive-letter slug, while the extension resolves the cwd with native fs.promises.realpath() (UNC path) and scans a slug that does not exist.
Two additions to the write-up above:
I realise the essentials of the below are already mentioned by the OP and later posters.
But this is a worked confirmation PLUS the cost of lost time and frustration to us
---------------------------
Confirming this on Windows 11 with a full mechanism, a regression window, a
same-machine control, and a working workaround.
Setup: workspace on a mapped SMB drive (
W:->\\NAS\workspace), VS Codeextension 2.1.214, CLI 2.1.215. Other workspaces on local drives, same machine,
same VS Code instance.
Mechanism
The extension and the CLI derive different project directory names for the same
workspace, because one resolves the mapped drive letter to its UNC target and the
other does not. Running each through the slug function in the shipped
extension.js:The CLI writes transcripts to
~/.claude/projects/w--/. The extension looks in~/.claude/projects/--NAS-workspace/, which has never existed, finds nothing,and returns an empty list.
Confirmed by workaround
Creating the UNC-derived directory as a junction to the real one immediately
restored the full session list in the VS Code panel:
After this, both surfaces work and stay unified — a new chat started in VS Code
wrote its transcript through the junction into
w--, alongside the CLI's. Noduplicate directory was created.
That the workaround works is the proof of the mechanism: nothing else changed.
Same-machine control
In the same VS Code instance, workspaces on local drives list their sessions
correctly (
E:,S:). Only the mapped network drive is affected. Local driveshave no UNC target, so both surfaces derive the same slug.
Regression window
This is a regression, not long-standing behaviour:
The transcript never moved; the lookup path did. The workspace had been working
normally for months prior.
The log makes this undiagnosable
This is the part worth fixing regardless of the path bug. The extension log shows
the request going out and simply never being answered — no result, no error, no
indication of what it looked for:
list_remote_sessionsreports its result.list_sessions_requestnever does.Logging the resolved project directory once per lookup would have reduced this
from a multi-hour investigation to about thirty seconds. The single line
looking for sessions in ~/.claude/projects/--NAS-workspacenames the bugoutright.
Cost of diagnosing this
Roughly four hours of one working day, by a user who had already lost time to
the same underlying class of problem earlier that morning. That included:
extension.jsto find the slug functionsymptoms, before the actual cause here was identified
None of the obvious remedies helped, and each cost a test cycle: merging the
duplicate case-variant keys in
~/.claude.json, renaming the project directoryto match the extension's expected case, setting
hasTrustDialogAccepted,matching CLI and extension versions, clearing orphaned session directories.
The failure presents as "all my history is gone" with no error anywhere in the
UI, so the natural user response is to assume data loss and start trying to
recover it. Every recovery attempt is wasted, because the transcripts were intact
on disk the entire time and
claude --resume <id>worked throughout.Suggested fixes, in order of value
one-line change that makes the entire bug class self-diagnosing.
both resolve to UNC, or neither does. Any consistent choice fixes this.
very different from "no sessions", and only the first is actionable.
before returning empty — that alone would have made this invisible to users.
Related
The same unnormalized-path-as-key pattern shows up across
#75855, #76994, #62288, #67749, #77837, #74912. This issue is the mapped-network-
drive variant. On the same machine, #77837 was also live: workspace trust granted
from the CLI as
W:/did not apply to the extension'sw:/, silently voiding 9permissions.allowrules with only a debug-log mention.Confirming this is still present in extension v2.1.226 (Windows 11 Pro 10.0.26200, VS Code 1.132.0, workspace on a mapped SMB drive
Z:→\\fileserver\share).Adding the exact minified call chain in the shipped
extension.js, since the identifiers have changed since the earlier reports (the slug function isyFin 2.1.226, notmx). All snippets below are verbatim from~/.vscode/extensions/anthropic.claude-code-2.1.226-win32-x64/extension.js.The divergence is inside a single dispatch function
tde()is the one entry point for session enumeration, and its two branches canonicalize the cwd differently:Branch A —
sessionStore(drive-letter-preserving). Usesrde(), which calls the JSrealpathSync:Branch B — filesystem (
PAt→RAt, UNC-expanding). Usesble(), which calls the native-backedfs.promises.realpath:So the same bundle contains two canonicalizers that can never agree on a mapped drive. Every
projectKeyproducer in the session-store path goes throughCx()(drive letter):Where the failure is swallowed
Db()is the only place the derived directory is touched, and itsreaddirfailure is caught and discarded, so the caller sees an ordinary empty list rather than an error:with the slug/path helpers:
Entry point from the RPC handler:
Full chain:
listSessions()→tde()→PAt()→RAt()→ble()(UNC) →Db()→ ENOENT →[].Minimal reproduction, no Claude Code involved
The underlying Node divergence, on Node v24.11.0 with
Z:mapped to\\fileserver\share:Feeding both through
yF():Everything downstream is healthy
I reimplemented the rest of the picker against the real transcripts to rule out a second cause. All of it passes; the directory resolution is the only fault:
getHiddenSessionIds()—hiddenSessionIdsis absent from the extension's VS CodeglobalState, so the filter inlistSessions()is inert.includeProgrammaticis false, but the caller passesincludeProgrammaticSessionshardcoded!0.Ljt(e,t){let r=FB(t);if(!r)return!0; ... }withDjt=/[/\\]\.claude[/\\]worktrees[/\\]([^/\\]+)$/. A non-worktree cwd makesFB(t)undefined, so this returns!0for every session.Sle()/ head-tail readervle()($c = 65536) — replayed against every transcript in the project; none returnsnull, andcwd, title andisSidechainall extract correctly.Replaying
RAt → Db → Cb → Cleverbatim returns0sessions before the junction and all3after, with no other change.One symptom worth calling out for triage
The currently-open session still appears in the panel, because the webview holds it as the live session object rather than as a result of the query. So the list looks like it has "one recent session" instead of looking broken — which reads as a stale cache and sends people toward Developer: Reload Window, which of course does nothing. That misdirection is probably why this keeps getting rediagnosed.
Suggested fix
Point
ble()at the same canonicalizationrde()already uses, so both branches oftde()agree. Failing that, haveDb()also probe theCx()/raw-cwd slug when theble()-derived directory is missing — that second form additionally repairs history for users who already have transcripts split across both slugs.The NTFS junction workaround from the original report still works on 2.1.226. It has to be recreated per project per mapped drive, which gets tedious with several mapped drives on one machine.