fix(core): handle EISDIR on virtual drives in memory discovery (#26985)

This commit is contained in:
Coco Sheng
2026-05-13 13:41:49 -04:00
committed by GitHub
parent 1e7063bb0b
commit 63b4bbfb5d
2 changed files with 46 additions and 4 deletions
@@ -46,6 +46,15 @@ import { Config, type GeminiCLIExtension } from '../config/config.js';
import { Storage } from '../config/storage.js';
import { SimpleExtensionLoader } from './extensionLoader.js';
import { CoreEvent, coreEvents } from './events.js';
import * as fs from 'node:fs';
vi.mock('node:fs', async (importOriginal) => {
const actual = await importOriginal<typeof import('node:fs')>();
return {
...actual,
realpathSync: vi.fn(actual.realpathSync),
};
});
vi.mock('os', async (importOriginal) => {
const actualOs = await importOriginal<typeof os>();
@@ -743,6 +752,40 @@ included directory memory
expect(result.filePaths).toContain(projectContextFile);
});
it('should not crash when fs.realpathSync throws EISDIR on virtual drive roots', async () => {
// Mock realpathSync to throw EISDIR for a specific path, simulating
// the Windows virtual drive issue (#25216).
vi.mocked(fs.realpathSync).mockImplementation((p: fs.PathLike) => {
const pathStr = p.toString();
if (pathStr.includes('virtual-drive')) {
const error = new Error(
"EISDIR: illegal operation on a directory, realpath 'A:\\a'",
);
(error as NodeJS.ErrnoException).code = 'EISDIR';
throw error;
}
// For other paths, we need to return something sensible.
// Since it's a mock, we can just return the path string itself.
return pathStr;
});
const virtualDriveCwd = path.join(testRootDir, 'virtual-drive');
await fsPromises.mkdir(virtualDriveCwd, { recursive: true });
// This should now succeed instead of throwing
const result = await loadServerHierarchicalMemory(
virtualDriveCwd,
[],
new FileDiscoveryService(projectRoot),
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
);
expect(result).toBeDefined();
expect(result.fileCount).toBe(0);
});
it('silently skips a GEMINI.md symlink that points to a directory', async () => {
// Create a real directory elsewhere and symlink GEMINI.md to it.
const realDir = await createEmptyDir(path.join(cwd, '.geminimd-target'));
+3 -4
View File
@@ -24,6 +24,7 @@ import {
isSubpath,
normalizePath,
toAbsolutePath,
resolveToRealPath,
} from './paths.js';
import type { ExtensionLoader } from './extensionLoader.js';
import { debugLogger } from './debugLogger.js';
@@ -702,10 +703,8 @@ export async function loadServerHierarchicalMemory(
boundaryMarkers: readonly string[] = ['.git'],
): Promise<LoadServerHierarchicalMemoryResponse> {
// FIX: Use real, canonical paths for a reliable comparison to handle symlinks.
const realCwd = normalizePath(
await fs.realpath(path.resolve(currentWorkingDirectory)),
);
const realHome = normalizePath(await fs.realpath(path.resolve(homedir())));
const realCwd = normalizePath(resolveToRealPath(currentWorkingDirectory));
const realHome = normalizePath(resolveToRealPath(homedir()));
const isHomeDirectory = realCwd === realHome;
// If it is the home directory, pass an empty string to the core memory