fix(core): resolve symlinks when normalizing project paths

Project registry identity was based on path.resolve() which does not follow symlinks. This caused different symlink paths to the same physical directory to be treated as different projects, leading to separate session stores.

Fixed by using resolveToRealPath() which correctly resolves symbolic links.

Closes #27278
This commit is contained in:
Akhilesh Kumar
2026-05-21 16:55:19 +00:00
parent 50c2e6764f
commit 8916cd742e
2 changed files with 18 additions and 1 deletions
@@ -435,4 +435,20 @@ describe('ProjectRegistry', () => {
expect(data.projects[normalizePath(projectPath)]).toBe('my-project');
expect(Object.values(data.projects)).not.toContain('../../etc/passwd');
});
it('resolves symlinks to the same short ID', async () => {
const registry = new ProjectRegistry(registryPath);
await registry.initialize();
const realDir = path.join(tempDir, 'real-project');
fs.mkdirSync(realDir);
const symlinkDir = path.join(tempDir, 'symlink-project');
fs.symlinkSync(realDir, symlinkDir, 'dir');
const id1 = await registry.getShortId(realDir);
const id2 = await registry.getShortId(symlinkDir);
expect(id1).toBe(id2);
});
});
+2 -1
View File
@@ -12,6 +12,7 @@ import { lock } from 'proper-lockfile';
import { z } from 'zod';
import { debugLogger } from '../utils/debugLogger.js';
import { isNodeError } from '../utils/errors.js';
import { resolveToRealPath } from '../utils/paths.js';
export interface RegistryData {
projects: Record<string, string>;
@@ -93,7 +94,7 @@ export class ProjectRegistry {
}
private normalizePath(projectPath: string): string {
let resolved = path.resolve(projectPath);
let resolved = resolveToRealPath(projectPath);
if (os.platform() === 'win32') {
resolved = resolved.toLowerCase();
}