[BUG] Windows drive-letter case not canonicalized in project keys — duplicate .claude.json / installed_plugins.json entries; VS Code trust silently dropped
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'sfsPathlowercases the drive letter →c:\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:
- Workspace trust silently dropped in VS Code. I accepted the trust dialog (my
~/.claude.jsoncontains"C:/Users/.../api-specs": { hasTrustDialogAccepted: true }and"C:\\Users\\...\\api-specs": { ... }). But the VS Code extension computes the keyc:/Users/.../api-specs(lowercase, fromfsPath), which doesn't match, so trust reads asfalseand 32permissions.allowentries from.claude/settings.jsonwere dropped. The error even says "workspace not yet trusted" when in fact it was trusted twice — under a different-cased key.
- Duplicate / mixed-case entries in
installed_plugins.json.projectPathandinstallPathare stored raw (native backslashes, original case, never normalized), so the same file showsC:\\Users\\...on one line andc:\\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:
- 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.jsonnow hasprojects["C:/Users/you/proj"].hasTrustDialogAccepted = true. - Open the same folder with the Claude Code VS Code extension (which spawns the CLI with
cwd = uri.fsPath, lowercasing the drive toc:\...). - Observe: the extension reports the workspace as untrusted and drops the project-scoped
permissions.allowentries from.claude/settings.json, even though it was trusted in step 1. The error citesprojects["c:/Users/you/proj"](lowercase) — a different key than what was stored.
Plugins variant:
- Install a plugin scoped to a project from a lowercase-drive context (VS Code) →
installed_plugins.jsonstoresprojectPath: "c:\\Users\\...". - Inspect
installed_plugins.jsonafter any operation from an uppercase-drive context → you'll see mixedC:\\.../c:\\...entries (installPathupper,projectPathlower 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.json→projectsobject keys (trust,permissions.allow, onboarding state)~/.claude/plugins/installed_plugins.json→projectPath/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/workspaceTrustreferences inextension.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
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗