From 6befe42eb09633f3adb500ae906a0e1da3f15f64 Mon Sep 17 00:00:00 2001 From: Arham Amin <132888838+arhxam@users.noreply.github.com> Date: Tue, 11 Aug 2026 21:22:23 +0530 Subject: [PATCH] fix(shared): normalize a bare Windows drive root the same as C:\ / C:/ (#6189) Co-authored-by: Claude Opus 4.8 (1M context) --- packages/shared/src/path.test.ts | 12 ++++++++++++ packages/shared/src/path.ts | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/path.test.ts b/packages/shared/src/path.test.ts index 52a07aae7befb52ad32bd0ac6cbad63de7e9b2e6..799f225fb6e25cba3a265d24ae668b5b8ca588fd 100644 --- a/packages/shared/src/path.test.ts +++ b/packages/shared/src/path.test.ts @@ -4,6 +4,8 @@ import { isUncPath, isWindowsAbsolutePath, isWindowsDrivePath, + normalizeProjectPathForComparison, + normalizeProjectPathForDispatch, } from "./path.ts"; describe("path helpers", () => { @@ -31,4 +33,14 @@ describe("path helpers", () => { expect(isExplicitRelativePath("..\\repo")).toBe(true); expect(isExplicitRelativePath("~/repo")).toBe(false); }); + + it("normalizes a bare Windows drive root the same as one with a trailing separator", () => { + // `C:`, `C:\` and `C:/` all refer to the drive root and must compare equal. + expect(normalizeProjectPathForDispatch("C:")).toBe("C:\\"); + expect(normalizeProjectPathForComparison("C:")).toBe("c:\\"); + expect(normalizeProjectPathForComparison("C:")).toBe(normalizeProjectPathForComparison("C:\\")); + expect(normalizeProjectPathForComparison("C:")).toBe(normalizeProjectPathForComparison("C:/")); + // Non-root drive paths keep their trailing separator trimmed as before. + expect(normalizeProjectPathForDispatch("C:\\repo\\")).toBe("C:\\repo"); + }); }); diff --git a/packages/shared/src/path.ts b/packages/shared/src/path.ts index 66887d3f2ec302b8ed4fdfc26efb964afff38fdc..b6758d814aa83eab9ec9fb1bfdb41c13ba6400cf 100644 --- a/packages/shared/src/path.ts +++ b/packages/shared/src/path.ts @@ -22,7 +22,11 @@ export function isExplicitRelativePath(value: string): boolean { } function isRootPath(value: string): boolean { - return value === "/" || value === "\\" || /^[a-zA-Z]:[/\\]?$/.test(value); + // The drive separator is required: a bare `C:` is not the drive root (it + // means "current directory on C:"), and treating it as already-canonical + // would leave it as `C:` while `C:\` and `C:/` normalize to the drive root, + // so the same location would fail project identity/dedup comparisons. + return value === "/" || value === "\\" || /^[a-zA-Z]:[/\\]$/.test(value); } function trimTrailingPathSeparators(value: string): string { -- 2.54.0