[BUG] Windows drive-letter case not canonicalized in project keys — duplicate .claude.json / installed_plugins.json entries; VS Code trust silently dropped

Open 💬 2 comments Opened Jul 8, 2026 by jeremyfiel

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?

On Windows, project paths are used as object keys in ~/.claude.json (projects) and in ~/.claude/plugins/installed_plugins.json (projectPath / installPath), but the drive letter is not case-canonicalized before the key is stored or compared. The stored case depends entirely on which surface launched the process:

  • VS Code extension spawns the CLI with cwd: workspaceFolder.uri.fsPath, and VS Code's fsPath lowercases the drive letterc:\Users\...
  • git-bash / MINGW / Cygwin paths (/c/..., /cygdrive/c/...) are run through a POSIX→Windows translator that uppercases the drive → C:\Users\...
  • PowerShell / cmd already report uppercase C:\...

Because lookups are case-sensitive (projects?.[key] and projectPath === o), the same physical directory ends up stored under two different keys (c:/... and C:/...). Windows paths are case-insensitive, so these are the same directory — but Claude treats them as distinct.

Two concrete user-visible symptoms:

  1. Workspace trust silently dropped in VS Code. I accepted the trust dialog (my ~/.claude.json contains "C:/Users/.../api-specs": { hasTrustDialogAccepted: true } and "C:\\Users\\...\\api-specs": { ... }). But the VS Code extension computes the key c:/Users/.../api-specs (lowercase, from fsPath), which doesn't match, so trust reads as false and 32 permissions.allow entries from .claude/settings.json were dropped. The error even says "workspace not yet trusted" when in fact it was trusted twice — under a different-cased key.
  1. Duplicate / mixed-case entries in installed_plugins.json. projectPath and installPath are stored raw (native backslashes, original case, never normalized), so the same file shows C:\\Users\\... on one line and c:\\Users\\... on the next. Case-sensitive === lookups then miss, causing duplicate entries / re-installs.

What Should Happen?

The drive letter (and ideally the whole path) should be canonicalized to a single case before being used as a stored key or in any equality comparison, so that one physical directory maps to exactly one entry regardless of launch surface (VS Code extension, git-bash, PowerShell, cmd).

Concretely: a workspace trusted once should stay trusted when opened from the VS Code extension, and a plugin installed once should not be duplicated/re-installed when the path case differs.

Error Messages/Logs

2026-07-08T19:36:36.105Z [DEBUG] Dropped 32 project-scoped permissions.allow entries — workspace not yet trusted
Ignoring 32 permissions.allow entries from .claude/settings.json: this workspace has not been trusted. Run Claude Code interactively here once and accept the trust dialog, or set projects["c:/Users/<user>/repos/api-specs"].hasTrustDialogAccepted: true in ~/.claude.json


Note the error references lowercase `c:/Users/...` (from VS Code `fsPath`), while `~/.claude.json` already contains a trusted entry keyed with uppercase `C:/Users/...`.

Steps to Reproduce

Root cause (from decompiled claude.exe) — the path-key normalizer folds separators but not drive case:

function doe(e){
  let t = normalize(e);
  if (platform === "windows") return t.replaceAll("\\", "/");  // separators only — drive case untouched
  return t;
}

Trust reader and writer both funnel through doe(), so they agree with each other — but doe()'s output is not canonical, so a different-cased cwd yields a different key:

// writer
function WIt(e){ let t = doe(path.resolve(e)); /* set projects[t].hasTrustDialogAccepted = true */ }
// reader
function Rse(){ let e = doe(...cwd...); return config.projects?.[e]?.hasTrustDialogAccepted === true; }

The case divergence comes from a POSIX→Windows translator that uppercases the drive, which only fires for POSIX-shaped input (git-bash/Cygwin) and not for native fsPath:

// /cygdrive/c/... and /c/... → C:\...   (uppercased)
// c:\... (VS Code fsPath) passes through unchanged → stays lowercase

Reproduction:

  1. On Windows, open a project in a terminal where the drive is uppercase (PowerShell, cmd, or git-bash — git-bash uppercases via the translator). Run claude, accept the trust dialog. → ~/.claude.json now has projects["C:/Users/you/proj"].hasTrustDialogAccepted = true.
  2. Open the same folder with the Claude Code VS Code extension (which spawns the CLI with cwd = uri.fsPath, lowercasing the drive to c:\...).
  3. Observe: the extension reports the workspace as untrusted and drops the project-scoped permissions.allow entries from .claude/settings.json, even though it was trusted in step 1. The error cites projects["c:/Users/you/proj"] (lowercase) — a different key than what was stored.

Plugins variant:

  1. Install a plugin scoped to a project from a lowercase-drive context (VS Code) → installed_plugins.json stores projectPath: "c:\\Users\\...".
  2. Inspect installed_plugins.json after any operation from an uppercase-drive context → you'll see mixed C:\\... / c:\\... entries (installPath upper, projectPath lower in the same file), and case-sensitive lookups (l.projectPath === o) miss, producing duplicates.

Suggested fix: canonicalize the drive letter (and folded case) inside doe(), e.g. t.replace(/^([a-z]):/, (_,d) => d.toUpperCase() + ":"), and route plugin projectPath/installPath storage + comparison through the same canonicalizer instead of storing/comparing raw strings. Since trust read+write both go through doe(), that single choke point fixes the trust duplication; the plugins file needs the same normalization applied at its store/compare sites (it currently bypasses doe() entirely). related #74912 #74612

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

n/a

Claude Code Version

2.1.204

Platform

AWS Bedrock

Operating System

Windows

Terminal/Shell

VS Code integrated terminal

Additional Information

(Also reproducible cross-surface: the mismatch specifically arises between the VS Code extension's fsPath cwd and git-bash/PowerShell launches.)

---

Additional Information

  • The bug spans multiple config files, so a fix should target the shared path-key normalization rather than any single call site:
  • ~/.claude.jsonprojects object keys (trust, permissions.allow, onboarding state)
  • ~/.claude/plugins/installed_plugins.jsonprojectPath / installPath (stored raw, backslashes + original case)
  • The whole binary has no drive-letter case-folding anywhere on these paths (grep for normalizeDriveLetter / drive case-fold → none); doe() only swaps \/.
  • The user-facing error message is misleading: it says "workspace not yet trusted" when the workspace was trusted, just under a differently-cased key. If detectable, it would help to say "trusted under a different-cased path" and point at the case mismatch.
  • The VS Code extension itself contains no trust-handling logic (no hasTrustDialogAccepted / workspaceTrust references in extension.js); it fully delegates to the CLI's cwd-derived key — so the only reliable user workaround today is hand-adding the lowercase-drive key to ~/.claude.json, which is not discoverable from the extension UI.

related #67749 but my issue is based on Workspace Trust and Plugin loading, not MCPs, specifically.
related #69066
area:cli
area:vscode
area:plugins

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗