Skip to content

Commit 63b4bbf

Browse files
authored
fix(core): handle EISDIR on virtual drives in memory discovery (#26985)
1 parent 1e7063b commit 63b4bbf

2 files changed

Lines changed: 46 additions & 4 deletions

File tree

packages/core/src/utils/memoryDiscovery.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ import { Config, type GeminiCLIExtension } from '../config/config.js';
4646
import { Storage } from '../config/storage.js';
4747
import { SimpleExtensionLoader } from './extensionLoader.js';
4848
import { CoreEvent, coreEvents } from './events.js';
49+
import * as fs from 'node:fs';
50+
51+
vi.mock('node:fs', async (importOriginal) => {
52+
const actual = await importOriginal<typeof import('node:fs')>();
53+
return {
54+
...actual,
55+
realpathSync: vi.fn(actual.realpathSync),
56+
};
57+
});
4958

5059
vi.mock('os', async (importOriginal) => {
5160
const actualOs = await importOriginal<typeof os>();
@@ -743,6 +752,40 @@ included directory memory
743752
expect(result.filePaths).toContain(projectContextFile);
744753
});
745754

755+
it('should not crash when fs.realpathSync throws EISDIR on virtual drive roots', async () => {
756+
// Mock realpathSync to throw EISDIR for a specific path, simulating
757+
// the Windows virtual drive issue (#25216).
758+
vi.mocked(fs.realpathSync).mockImplementation((p: fs.PathLike) => {
759+
const pathStr = p.toString();
760+
if (pathStr.includes('virtual-drive')) {
761+
const error = new Error(
762+
"EISDIR: illegal operation on a directory, realpath 'A:\\a'",
763+
);
764+
765+
(error as NodeJS.ErrnoException).code = 'EISDIR';
766+
throw error;
767+
}
768+
// For other paths, we need to return something sensible.
769+
// Since it's a mock, we can just return the path string itself.
770+
return pathStr;
771+
});
772+
773+
const virtualDriveCwd = path.join(testRootDir, 'virtual-drive');
774+
await fsPromises.mkdir(virtualDriveCwd, { recursive: true });
775+
776+
// This should now succeed instead of throwing
777+
const result = await loadServerHierarchicalMemory(
778+
virtualDriveCwd,
779+
[],
780+
new FileDiscoveryService(projectRoot),
781+
new SimpleExtensionLoader([]),
782+
DEFAULT_FOLDER_TRUST,
783+
);
784+
785+
expect(result).toBeDefined();
786+
expect(result.fileCount).toBe(0);
787+
});
788+
746789
it('silently skips a GEMINI.md symlink that points to a directory', async () => {
747790
// Create a real directory elsewhere and symlink GEMINI.md to it.
748791
const realDir = await createEmptyDir(path.join(cwd, '.geminimd-target'));

packages/core/src/utils/memoryDiscovery.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
isSubpath,
2525
normalizePath,
2626
toAbsolutePath,
27+
resolveToRealPath,
2728
} from './paths.js';
2829
import type { ExtensionLoader } from './extensionLoader.js';
2930
import { debugLogger } from './debugLogger.js';
@@ -702,10 +703,8 @@ export async function loadServerHierarchicalMemory(
702703
boundaryMarkers: readonly string[] = ['.git'],
703704
): Promise<LoadServerHierarchicalMemoryResponse> {
704705
// FIX: Use real, canonical paths for a reliable comparison to handle symlinks.
705-
const realCwd = normalizePath(
706-
await fs.realpath(path.resolve(currentWorkingDirectory)),
707-
);
708-
const realHome = normalizePath(await fs.realpath(path.resolve(homedir())));
706+
const realCwd = normalizePath(resolveToRealPath(currentWorkingDirectory));
707+
const realHome = normalizePath(resolveToRealPath(homedir()));
709708
const isHomeDirectory = realCwd === realHome;
710709

711710
// If it is the home directory, pass an empty string to the core memory

0 commit comments

Comments
 (0)