VS Code extension can't find sessions on mapped network drives (realpath resolves to UNC)

Status Closed — not planned
Reported on v2.1.72
Maintainer reply None cached
Activity 12 comments · opened Mar 11, 2026 · closed Apr 30, 2026

Problem

On Windows, when the project lives on a mapped network drive (e.g. U:\projects\my-app), the VS Code extension's listSessions() returns no results because:

  1. The CLI creates session .jsonl files in ~/.claude/projects/u--projects-my-app/ (encoded from the drive letter path)
  2. The VS Code extension calls fs.realpath() which resolves the mapped drive to its UNC path (\fileserver.example.com\share\projects\my-app)
  3. The UNC path encodes to a completely different directory name (--fileserver-example-com-share-projects-my-app)
  4. No sessions are found ÔÇö the sidebar is empty after every restart

Impact

  • All sessions created in VS Code disappear on restart
  • Sessions created in the terminal CLI are never visible in VS Code
  • Affects any Windows user whose workspace is on a mapped network drive (common in studios/enterprises with DFS/SMB shares)

Workaround

Create a symlink between the two directory names in ~/.claude/projects/:

mklink /D "%USERPROFILE%\.claude\projects\--fileserver-example-com-share-projects-my-app" "%USERPROFILE%\.claude\projects\u--projects-my-app"

Suggested fix

Either skip realpath() on Windows mapped drives, or normalize both the CLI and extension to use the same path before encoding the project directory name.

Environment

  • Windows 10/11, Claude Code 2.1.72
  • Mapped network drive (DFS/SMB)

View original on GitHub ↗

12 Comments

github-actions[bot] · 5 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/31219
  2. https://github.com/anthropics/claude-code/issues/14088
  3. https://github.com/anthropics/claude-code/issues/31535

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

BenNewman100 · 5 months ago

Yes, I have the same issue and I tested and confirmed this myself manually (not having Claude do it). I just used Claude to write the below report. Also these are the real paths I tests. I didn't redact them.

----

Title: VS Code Extension does not show resume session list for sessions on SMB mapped network drives

Body:

Summary

The VS Code Claude Code Extension fails to list previous sessions in the "Resume Session" picker when the working directory is on an SMB network share mapped to a drive letter. Sessions on local drives (including additional physical drives) work correctly. Sessions on the network drive are being saved to ~/.claude/projects/ with correct folder names and content — they just never appear in the resume list.

Environment

  • Windows 11 Pro
  • Claude Code Extension v2.1.72
  • L:\ drive = SMB file share mapped to a drive letter (\\BEN-LAPTOP-MAIN\L (All My Files))
  • T:\ drive = local physical drive (added to VM for testing)
  • C:\ drive = local system drive

Network Drive Details

L:\ is mapped to UNC path \\BEN-LAPTOP-MAIN\L (All My Files) (note: share name contains spaces and parentheses).

Test Sessions

I manually created test sessions by opening VS Code in each directory and starting a Claude Code chat. I then closed and reopened VS Code in the same directory to check if previous sessions appeared in the resume list.

| # | Working Directory | Drive Type | Resume Works? |
|---|---|---|---|
| 1 | C:\Users\Codesys-Dev | Local (C:) | Yes |
| 2 | c:\Users\Codesys-Dev\Desktop | Local (C:) | Yes |
| 3 | c:\Users\Codesys-Dev\Desktop\New folder | Local (C:) | Yes |
| 4 | c:\Program Files (x86)\Windows Media Player\Media Renderer | Local (C:) | Yes |
| 5 | c:\This Is A Long Directory Name\...\Final Level Directory Name Here OK | Local (C:) | Yes |
| 6 | l:\ | SMB mapped → \\BEN-LAPTOP-MAIN\L (All My Files) | No |
| 7 | l:\Repos\Professional\Experiments\HMI_Project_Refactoring_Tool | SMB mapped → \\BEN-LAPTOP-MAIN\L (All My Files)\Repos\... | No |
| 8 | t:\New folder\New folder 2 | Local physical (T:) | Yes |

Key Observations

  • All C:\ and T:\ sessions appear in the resume list, including paths with spaces, parentheses, and very long deeply nested directory names.
  • No L:\ sessions appear in the resume list — neither the drive root nor subdirectories.
  • Session .jsonl files are being written correctly to ~/.claude/projects/ for all drives, including L:\. The data is there; the extension just doesn't find it.
  • T:\ (a non-C: local physical drive) works fine, ruling out "non-C: drive" as the issue. The problem is specific to SMB/network mapped drives.

Possible Cause

The extension may be resolving the mapped drive letter to a UNC path (e.g., \\BEN-LAPTOP-MAIN\L (All My Files)\...) during session lookup, producing a different encoded folder name than what was used when the session was saved (which used the drive letter l:\...). Alternatively, there may be a filesystem API behavior difference on SMB shares affecting directory enumeration or file watching.

ROC2024-REAL · 5 months ago

Root cause: fs.realpathSync() / fs.realpath() resolve a mapped drive letter (e.g. I:\) to its UNC path (\\server\share\...). Sessions are saved under the drive-letter slug (I--Projects-...) but looked up under the UNC slug (--server-share-Projects-...), so they're never found.

Fix (tested on VS Code extension 2.1.73, Windows 11, workspace on mapped network drive):

In FS() and all realpathSync() calls — if the resolved path starts with \\ but the input started with a drive letter, return the original path:

async function FS(z) {
try {
let _r = (await d8.realpath(z)).normalize("NFC");
if (_r.startsWith("\\\\") && !z.startsWith("\\\\"))
return z.normalize("NFC");
return _r;
} catch {
return z.normalize("NFC");
}
}
Same pattern for realpathSync() in resolveSessionListView, resolveWebviewView, etc.

This fix resolved the issue on my end. Happy to provide a patch/PR if useful.

BoostlyPeter · 5 months ago

Confirmed on v2.1.76 (also v2.1.74). Same root cause — IS(v) / FS(z) uses fs/promises.realpath() which resolves drive letters to UNC on Windows.

@ROC2024-REAL's conditional check works, but there's a simpler fix: just swap to fs.realpathSync() (the sync require("fs") variant) instead. realpathSync does not resolve mapped drives to UNC on Windows — it returns the drive-letter path unchanged. No need to check for \ prefix.

// v2.1.76 — b2 = require("fs"), N3 = require("fs/promises"), both already in scope:
async function IS(v) {
  try { return b2.realpathSync(v).normalize("NFC") }
  catch { return v.normalize("NFC") }
}

// v2.1.74 — DH = require("fs"), o8 = require("fs/promises"):
async function FS(z) {
  try { return DH.realpathSync(z).normalize("NFC") }
  catch { return z.normalize("NFC") }
}

The sync and async fs variants behave differently on Windows for mapped drives specifically: fs.promises.realpath() resolves via Win32 GetFinalPathNameByHandle which returns UNC; fs.realpathSync() uses a different code path that preserves the drive letter.

Confirmed effect: listSessions() returns the correct count immediately (n=11 in my case). Session restore works across VS Code restarts.

Scope is wider than the Past Conversations dropdown — the same IS()/FS() call is used when restoring VS Code panel tabs via deserializeWebviewPanel. With n=0, activateSessionFromServer silently fails and the panel stays blank on restart. Same fix resolves both symptoms.

See also: #34125 (same root cause, filed last week) — I've posted the tested fix there with more detail.

ROC2024-REAL · 5 months ago

Confirmed on v2.1.77 as well — your realpathSync approach is indeed cleaner. I've verified it locally:

fs.realpathSync('I:/Projets/Python/Traducteur')
// → 'I:\Projets\Python\Traducteur' ← drive letter preserved

await fs.promises.realpath('I:/Projets/Python/Traducteur')
// → '\\vsrvdatas\Informatique\Projets\...' ← UNC via GetFinalPathNameByHandle
No need for the startsWith("\\") guard — just swap the call.

I've been patching this since v2.1.72 and the main annoyance is minified names changing every release (IS→XP, v→z, N3→x3…). Here's a generic Python script that finds the right function/variables dynamically and survives version bumps. Run it after each extension update:

import re, glob

Find latest installed version

exts = glob.glob('<VSCODE_EXTENSIONS>/anthropic.claude-code-*-win32-x64')
ext_dir = sorted(exts)[-1]
print(f'Extension: {ext_dir}')

ext_js = f'{ext_dir}/extension.js'
with open(ext_js, 'r', encoding='utf-8') as f:
c = f.read()

patched = False

Detect the async realpath function (minified names vary per version)

Pattern: async function XX(p){try{return(await FS_PROMISES.realpath(p)).normalize("NFC")}catch{return p.normalize("NFC")}}

m = re.search(
r'async function (\w+)\((\w+)\)\{try\{return\(await (\w+)\.realpath\(\2\)\)'
r'\.normalize\("NFC"\)\}catch\{return \2\.normalize\("NFC"\)\}\}',
c
)
if m:
func_name, param, promises_var = m.group(1), m.group(2), m.group(3)

# Find the sync fs variable — the one already used in realpathSync calls
m_sync = re.search(r'(\w+)\.realpathSync\(\w+\[0\]\|\|\w+\.homedir\(\)\)', c)
if m_sync:
sync_var = m_sync.group(1)
old = m.group(0)
new = (
f'async function {func_name}({param})'
f'{{try{{return {sync_var}.realpathSync({param}).normalize("NFC")}}'
f'catch{{return {param}.normalize("NFC")}}}}'
)
c = c.replace(old, new)
print(f' [OK] {func_name}({param}): {promises_var}.realpath -> {sync_var}.realpathSync')
patched = True
else:
print(' [!!] Could not find sync fs variable')
else:
# Check if already patched
if re.search(r'async function \w+\(\w+\)\{try\{return \w+\.realpathSync', c):
print(' [--] Already patched')
else:
print(' [!!] async realpath function not found — pattern may have changed')

if patched:
with open(ext_js, 'w', encoding='utf-8') as f:
f.write(c)

print('Done. Restart VS Code.')
Tested across v2.1.72, v2.1.73, v2.1.76, v2.1.77 — regex picks up the right function each time.

Good callout on deserializeWebviewPanel scope — I was only tracking listSessions() but that explains the blank panel on restart.

BoostlyPeter · 5 months ago

Update for v2.1.78: The function was renamed and variables changed again. New targets:

// v2.1.78 — function renamed pP, async fs = M3, sync fs = Iq:
async function pP(z) {
  try { return Iq.realpathSync(z).normalize("NFC") }
  catch { return z.normalize("NFC") }
}

Summary across versions:

| Version | Function | Async fs var | Sync fs var |
|---------|----------|-------------|-------------|
| v2.1.74 | FS(z) | o8 | DH |
| v2.1.76 | IS(v) | N3 | b2 |
| v2.1.78 | pP(z) | M3 | Iq |

All three sync vars are require("fs") — already in scope. The fix pattern is identical across versions; only the names change.

BoostlyPeter · 5 months ago

Update for v2.1.79: Only the function name changed again. All other variables identical to v2.1.78.

| Version | Function | Async fs var | Sync fs var |
|---------|----------|-------------|-------------|
| v2.1.74 | FS(z) | o8 | DH |
| v2.1.76 | IS(v) | N3 | b2 |
| v2.1.78 | pP(z) | M3 | Iq |
| v2.1.79 | hP(z) | M3 | Iq |

// v2.1.79:
async function hP(z) {
  try { return Iq.realpathSync(z).normalize("NFC") }
  catch { return z.normalize("NFC") }
}

The function has been renamed in every minor release since v2.1.76. The fix pattern is always the same — swap the async fs/promises call for the sync fs equivalent that's already in scope.

BoostlyPeter · 5 months ago

Update for v2.1.81: Multiple variable changes. Updated table:

| Version | Function | Async fs var | Sync fs var |
|---------|----------|-------------|-------------|
| v2.1.74 | FS(z) | o8 | DH |
| v2.1.76 | IS(v) | N3 | b2 |
| v2.1.78 | pP(z) | M3 | Iq |
| v2.1.79 | hP(z) | M3 | Iq |
| v2.1.81 | hP(z) | F3 | bW |

v2.1.81 kept the function name hP but changed both fs variable names (M3F3, IqbW). Confirmed patched and working across 11 user accounts (v2.1.74, v2.1.76, v2.1.81), 0 failures.

BoostlyPeter · 5 months ago

Update for v2.1.83: Function renamed again, parameter changed, both fs variables changed.

| Version | Function | Async fs var | Sync fs var |
|---------|----------|-------------|-------------|
| v2.1.74 | FS(z) | o8 | DH |
| v2.1.76 | IS(v) | N3 | b2 |
| v2.1.78 | pP(z) | M3 | Iq |
| v2.1.79 | hP(z) | M3 | Iq |
| v2.1.81 | hP(z) | F3 | bW |
| v2.1.83 | KT(K) | i1 | WN |

// v2.1.83:
async function KT(K) {
  try { return WN.realpathSync(K).normalize("NFC") }
  catch { return K.normalize("NFC") }
}

Note: v2.1.83 also changed the function parameter from z to K, and both the async and sync fs variable names changed. The function name, parameter, and fs variables have now all changed simultaneously — the only stable pattern across versions is the function signature shape and the .normalize("NFC") calls.

Confirmed patched and working on v2.1.83 across 16 user accounts (spanning v2.1.74 through v2.1.83), 0 failures.

BoostlyPeter · 5 months ago

Update for v2.1.87: Function renamed again, all variables changed.

| Version | Function | Async fs var | Sync fs var |
|---------|----------|-------------|-------------|
| v2.1.74 | FS(z) | o8 | DH |
| v2.1.76 | IS(v) | N3 | b2 |
| v2.1.78 | pP(z) | M3 | Iq |
| v2.1.79 | hP(z) | M3 | Iq |
| v2.1.81 | hP(z) | F3 | bW |
| v2.1.83 | KT(K) | i1 | WN |
| v2.1.87 | Oh(K) | w3 | uv |

// v2.1.87:
async function Oh(K) {
  try { return uv.realpathSync(K).normalize("NFC") }
  catch { return K.normalize("NFC") }
}

Seven versions tracked, bug still present and unfixed. Confirmed patched and working across 17 user accounts (v2.1.74 through v2.1.87), 0 failures.

github-actions[bot] · 4 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

github-actions[bot] · 3 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.