Great @Colin
Looking forward to the fix — it’s a really useful feature.
Great @Colin
Looking forward to the fix — it’s a really useful feature.
Cursor IDE
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.
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.
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)
}
rootUri comes from the built-in git extension, which lowercases the drive letter when resolving the repository root (extensions/git/dist/main.js, getRepositoryRoot).folders[0].uri, from URI.file()) keeps whatever drive-letterThe workspace folder URI’s drive casing depends on how the folder is opened, not on the repo:
cursor CLI from a terminal (cursor . / cursor C:\path\to\repo): on Windows the shell’sC:\...), so the resulting folder URI/C:/... → mismatch → fails.file:///c%3A/... in globalStorage/storage.json) → folder URI is /c:/...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.
cwd → diff failsBecause 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.)
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.removedProjectsEach 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:
.path URIs are persisted (originally seeded by a CLI open from an uppercaseC:\ cwd), reopening the folder — even from the UI — rehydrates them with the uppercase .path,/C:/... and the match keeps failing. The failure therefore persists.code-workspace.workspace.json files and globalStorage/storage.json store only the lowercaseexternal form and are unaffected; the problematic data is specifically the serialized-URI.path) in globalStorage/state.vscdb.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);
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:
git rev-parse --show-toplevel reports ac:\...), which is the default on Windows.C:\..., the Windows default), open the repo via the CLI: cursor . or cursor C:\path\to\repo..path = /C:/...).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.
Changes view shows the changes and no error message
Windows 10/11
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
Sometimes - I can sometimes use Cursor
This method works: changing the drive letter of the project paths in the multi-root workspace configuration file to lowercase lets Cursor’s Agent window open the Git repository. Here are the steps:
%APPDATA%\Cursor\glassMultiRootWorkspaces, then open the configuration file xxx.yyy-workspace inside it:{
"folders": [
{
"path": "F:/test/Abc/module-a",
},
{
"path": "F:/test/Abc/module-b",
},
{
"path": "F:/test/Abc/module-c",
},
],
}
{
"folders": [
{
"path": "f:/test/Abc/module-a",
},
{
"path": "f:/test/Abc/module-b",
},
{
"path": "f:/test/Abc/module-c",
},
],
}
Thanks, @bittwist
Really helpful stuff @bittwist and @codepower! Sharing with the team.
Also seeing this issue. Looking forward to a fix.
With some AI help I put together this script which rewrites the bad data inside the globalStorage. Thanks to @bittwist for some of the details.
const fs = require('fs');
const path = require('path');
const sqlite3 = require('sqlite3').verbose();
// 1. Resolve the database path using the Windows APPDATA environment variable
const appData = process.env.APPDATA;
if (!appData) {
console.error("Error: APPDATA environment variable is not defined. Ensure you are running this on Windows.");
process.exit(1);
}
const dbPath = path.join(appData, 'Cursor', 'User', 'globalStorage', 'state.vscdb');
const backupPath = `${dbPath}.backup-${Date.now()}`;
// 2. Check if the database exists and create a backup
if (!fs.existsSync(dbPath)) {
console.error(`Error: Database not found at: ${dbPath}`);
process.exit(1);
}
console.log(`Creating backup at: ${backupPath}...`);
try {
fs.copyFileSync(dbPath, backupPath);
console.log("✅ Backup created successfully.\n");
} catch (err) {
console.error("❌ Failed to create backup:", err);
process.exit(1);
}
// 3. Connect to the SQLite Database
const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READWRITE, (err) => {
if (err) {
console.error("Error opening database:", err.message);
process.exit(1);
}
});
const updatedKeys = [];
const updateQuery = "UPDATE ItemTable SET value = ? WHERE key = ?";
// 4. Process the database
db.serialize(() => {
db.all("SELECT key, value FROM ItemTable", [], (err, rows) => {
if (err) {
console.error("Error querying ItemTable:", err.message);
db.close();
return;
}
console.log(`Found ${rows.length} items in ItemTable. Searching for 'C:/' or 'C:\\'...`);
// Prepare the statement for bulk updates
const stmt = db.prepare(updateQuery);
let matchCount = 0;
rows.forEach((row) => {
const { key, value } = row;
if (!value) return;
// Convert BLOB (Buffer) to a string to perform the regex search
const isBuffer = Buffer.isBuffer(value);
const originalStr = isBuffer ? value.toString('utf8') : value.toString();
// Regex looks for "C:" followed by either a forward slash or a backslash
const regex = /C:([\\\/])/g;
if (regex.test(originalStr)) {
matchCount++;
// Reset regex state after .test() because of the global 'g' flag
regex.lastIndex = 0;
// Replace 'C' with 'c', keeping the captured slash character intact ($1)
const newStr = originalStr.replace(regex, 'c:$1');
// Convert back to Buffer if it was originally a Buffer
const newValue = isBuffer ? Buffer.from(newStr, 'utf8') : newStr;
// Execute the update
stmt.run([newValue, key], (updateErr) => {
if (updateErr) {
console.error(`❌ Failed to update key: ${key}`, updateErr);
} else {
updatedKeys.push(key);
}
});
}
});
// 5. Finalize the statement and report results
stmt.finalize(() => {
console.log("\n--- Processing Complete ---");
console.log(`Total rows checked: ${rows.length}`);
console.log(`Total rows with matching values: ${matchCount}`);
if (updatedKeys.length > 0) {
console.log("\n✅ The following keys were successfully updated:");
updatedKeys.forEach(k => console.log(` - ${k}`));
} else {
console.log("\nℹ️ No values matching 'C:/' or 'C:\\' were found. No changes made.");
}
// Close the database connection safely
db.close((closeErr) => {
if (closeErr) {
console.error("Error closing database:", closeErr.message);
} else {
console.log("\nDatabase connection closed.");
}
});
});
});
});
you’ll need to npm install sqlite3 in whatever folder you run this. It should automatically backup yoru db first and find it automatically.
Has there been any update to this?
Hello everyone,
please prioritise this issue: every time I need to review changes to an agent’s code, I have to switch to another IDE, and the suggestion to switch to the classic Cursor editor window is not at all practical when working on multiple workspaces in parallel – which is precisely the main advantage of the new agent window.
I’m not even using remote SSH; I’m using Cursor with local Git repositories, but I keep getting the error ‘Unable to load changes’ and even the ‘Review’ button no longer appears.
I can’t even follow the suggestion to ‘change the drive letter’ because my files are written with relative paths, such as: ‘path’: ‘../../../../../../ development/git/ms-event-hub’.
I understand that the team needs to prioritise critical bugs and strike a balance with new features, but, honestly, having a fantastic tool like Cursor (I really love it), paying for it, and having to resort to workarounds for a feature I use all the time is very frustrating.
I really appreciate the new features, but I think the team should also prioritise stability more highly.
Thank you very much and keep up the good work.