Where does the bug appear (feature/product)?
Cursor IDE
Describe the Bug
Note: I was able to fix this bug in cursor locally with cursor and can verify it works.
On Windows, the Changes view (the “Glass” diff panel) fails with “Failed to load changes”.
Root cause is in GlassDiffService (out/vs/workbench/workbench.glass.main.js): it matches the SCM git repository to the workspace
folder using a case-sensitive comparison of URI.path. The repository’s rootUri arrives with a lowercase drive letter (/c:/…) while
the workspace folder URI has an uppercase drive letter (/C:/…). The comparison therefore never matches, no repository is bound to the folder,
and the service is left without a working directory — so all subsequent diff calls run with an empty cwd and fail.
Observed in logs
renderer.log on failure:
[GlassDiffService] Refresh failed (reason=immediate, cwd=<empty>, gitRepoRoot=C:/<path>/<repo>): Failed to execute git
[GlassDiffService] Failed to refresh scope (scope=staged, cwd=<empty>): Failed to execute git
[GlassDiffService] Failed to refresh scope (scope=unstaged, cwd=<empty>): Failed to execute git
Note cwd=<empty> — the diff has no repository working directory.
Direct evidence (captured by temporarily instrumenting _resolveWorkspaceRootRepositories):
a failing repo vs a working repo on the same machine, same session —
# FAILING (folder URI drive is uppercase)
folder=/C:/<path>/<repoA> repo=/c:/<path>/<repoA> isGit=true caseSensitiveMatch=false caseInsensitiveMatch=true
# WORKING (folder URI drive is lowercase)
folder=/c:/<path>/<repoB> repo=/c:/<path>/<repoB> isGit=true caseSensitiveMatch=true caseInsensitiveMatch=true
caseSensitiveMatch=false is exactly the current (buggy) behavior; caseInsensitiveMatch=true
is what the fix produces.
Root cause (Cursor internals)
1. Case-sensitive repo↔folder matching
GlassDiffService._isUriAtOrInside compares URI.path literally, with no case normalization:
_isUriAtOrInside(n, e) {
if (n.scheme !== e.scheme || n.authority !== e.authority) return false;
const t = s => s.replace(/\/+$/, "") || "/",
i = t(n.path), // "/c:/<path>/<repo>" — repo rootUri, LOWERCASE drive
r = t(e.path); // "/C:/<path>/<repo>" — workspace folder uri, UPPERCASE drive
return r === i || (i === "/" ? r.startsWith("/") : r.startsWith(`${i}/`));
}
_resolveWorkspaceRootRepositories() uses it to associate each SCM repo with a workspace folder; the mismatch makes it return an empty list:
_resolveWorkspaceRootRepositories() {
const n = this._workspaceContextService.getWorkspace().folders, e = [];
for (const i of this._scmService.repositories) {
if (!this._isGitScmRepository(i)) continue;
const r = i.provider.rootUri; // "/c:/..." (lowercase)
const s = n.find(o => this._isUriAtOrInside(o.uri, r) || this._isUriAtOrInside(r, o.uri)); // "/C:/..." never matches "/c:/..."
s && (... push ...); // never pushed
}
return e; // -> [] (empty)
}
2. Why the two casings differ
- The repo
rootUricomes from the built-in git extension, which lowercases the drive letter when resolving the repository root (extensions/git/dist/main.js,getRepositoryRoot). - The workspace folder URI (
folders[0].uri, fromURI.file()) keeps whatever drive-letter
casing the open path had, and is not normalized to lowercase the way the git root is.
How the bug is triggered (origin of the uppercase drive)
The workspace folder URI’s drive casing depends on how the folder is opened, not on the repo:
cursorCLI from a terminal (cursor ./cursor C:\path\to\repo): on Windows the shell’s
working directory is reported with an uppercase drive (C:\...), so the resulting folder URI
is/C:/...→ mismatch → fails.- Open via the Cursor UI / restored window list: uses the persisted workspace URI, which is
stored lowercase (file:///c%3A/...inglobalStorage/storage.json) → folder URI is/c:/...
→ matches → works.
This is why two repos on the same machine behave differently: the failing one had first been
opened via the CLI (uppercase drive), the working one via the UI (lowercase drive). Note that the
uppercase form, once introduced, is then persisted (see “Persistence” below), so it sticks across
later reopens. Confirmed with the instrumented caseSensitiveMatch evidence above.
3. Empty list → empty cwd → diff fails
Because the repo list is empty, _rootUri is never set and _cwd stays at its initializer (""). The diff entry point then runs git with that empty cwd and no --git-dir/--work-tree:
async getDiffFileEntries(e) {
const t = ["--no-optional-locks", "diff", ...this._getDiffRangeArgs(e)];
...
this.executeGitCommandStable(e.cwd /* "" */, [...t, "--name-status", "-z", ...], { caller: "GitProvider.getDiffFileEntries.nameStatus" });
}
With an empty cwd, the diff cannot resolve the repository and fails; GlassDiffService classifies it as the generic "unknown" snapshot error → “Failed to load changes.”
(_getGitRootStatus() does separately recover a root via a fallback and stores it in _gitRepoRootFsPath — that’s the uppercase gitRepoRoot=C:/… seen in the log — but _cwd is never populated from it, so the diff path stays broken.)
Persistence (why the failure is sticky)
The uppercase-drive form is persisted in %APPDATA%\Cursor\User\globalStorage\state.vscdb, inside
serialized URI objects under several keys, e.g.:
composer.composerHeadersglass.localAgentProjects.v1workspaceMetadata.entriescursor/glass.additionalProjectscursor/glass.removedProjects
Each serialized URI looks like:
{ "external": "file:///c%3A/<path>/<repo>", "path": "/C:/<path>/<repo>", "scheme": "file" }
Note the split: external (canonical URI.toString()) is lowercase, but the .path field
retains the uppercase drive from when the URI was constructed via URI.file() with an uppercase
input path. _isUriAtOrInside compares .path, so it uses the uppercase value.
Consequences:
- Once these uppercase-
.pathURIs are persisted (originally seeded by a CLI open from an uppercase
C:\cwd), reopening the folder — even from the UI — rehydrates them with the uppercase.path,
so the folder URI stays/C:/...and the match keeps failing. The failure therefore persists
across restarts and is not cleared by reopening with a lowercase path or a.code-workspace. - The plain
workspace.jsonfiles andglobalStorage/storage.jsonstore only the lowercase
externalform and are unaffected; the problematic data is specifically the serialized-URI
blobs (with uppercase.path) inglobalStorage/state.vscdb.
Verified fix
Making _isUriAtOrInside compare paths case-insensitively fixes it. We patched out/vs/workbench/workbench.glass.main.js and confirmed the Changes view loads correctly afterward (renderer.log then shows a non-empty cwd=c:/… and no “Refresh failed”):
// before
const t = s => s.replace(/\/+$/, "") || "/",
i = t(n.path), r = t(e.path);
// after — lowercase both before comparing
const t = s => (s.replace(/\/+$/, "") || "/").toLowerCase(),
i = t(n.path), r = t(e.path);
Steps to Reproduce
The trigger is the drive-letter casing of the workspace folder URI. The failure occurs when
that URI’s .path has an uppercase drive (/C:/...), because the git repo rootUri.path
always has a lowercase drive (/c:/...); see Root cause. Important: once a folder is opened
with an uppercase drive, Cursor persists the uppercase form (see “Persistence” below), so the
failure then survives reopening the folder even via the UI — clearing the persisted state is
required to get back to a clean baseline.
Reliable repro from a clean state:
- Ensure no persisted uppercase URI exists for the repo yet (fresh repo never opened in Cursor,
or clear its entries — see Persistence). Confirmgit rev-parse --show-toplevelreports a
lowercase drive (c:\...), which is the default on Windows. - From a terminal whose working directory is an uppercase path (PowerShell reports the cwd as
C:\..., the Windows default), open the repo via the CLI:cursor .orcursor C:\path\to\repo.
This constructs the folder URI with an uppercase drive (.path=/C:/...). - Open the Changes view → “Failed to load changes”.
Contrast: opening the same repo so the folder URI is lowercase (/c:/...) — e.g. an entry whose
persisted path is lowercase — works. This is why two repos on the same machine differ purely by
how/when they were first opened. The Source Control view always works (it uses vscode.git
with an explicit --git-dir); only the Changes/Glass view fails.
Expected Behavior
Changes view shows the changes and no error message
Screenshots / Screen Recordings
Operating System
Windows 10/11
Version Information
Version: 3.8.24 (user setup)
VS Code Extension API: 1.105.1
Commit: cf80f4b937f3b9c48070d7085129a838ce7876a0
Date: 2026-06-24T06:55:08.142Z
Layout: editor
Build Type: Stable
Release Track: Default
Electron: 40.10.3
Chromium: 144.0.7559.236
Node.js: 24.15.0
V8: 14.4.258.32-electron.0
xterm.js: 6.1.0-beta.256
OS: Windows_NT x64 10.0.26100
Does this stop you from using Cursor
Sometimes - I can sometimes use Cursor
