authorgravatar for 132888838+arhxam@users.noreply.github.comArham Amin <132888838+arhxam@users.noreply.github.com> 2026-08-11 21:22:23+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-11 15:52:23+00:00
log6befe42eb09633f3adb500ae906a0e1da3f15f64
treefaec3e62eab38dce58e36c518170b5b4fa35821c
parentb30a9bc4143910f12d9c990c221fb9bc20cbc293
signature Signed by PGP key B5690EEEBB952194

fix(shared): normalize a bare Windows drive root the same as C:\ / C:/ (#6189)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

2 files changed, 17 insertions(+), 1 deletions(-)

packages/shared/src/path.test.ts+12
......@@ -4,6 +4,8 @@ import {
44 isUncPath,
55 isWindowsAbsolutePath,
66 isWindowsDrivePath,
7 normalizeProjectPathForComparison,
8 normalizeProjectPathForDispatch,
79} from "./path.ts";
810
911describe("path helpers", () => {
......@@ -31,4 +33,14 @@ describe("path helpers", () => {
3133 expect(isExplicitRelativePath("..\\repo")).toBe(true);
3234 expect(isExplicitRelativePath("~/repo")).toBe(false);
3335 });
36
37 it("normalizes a bare Windows drive root the same as one with a trailing separator", () => {
38 // `C:`, `C:\` and `C:/` all refer to the drive root and must compare equal.
39 expect(normalizeProjectPathForDispatch("C:")).toBe("C:\\");
40 expect(normalizeProjectPathForComparison("C:")).toBe("c:\\");
41 expect(normalizeProjectPathForComparison("C:")).toBe(normalizeProjectPathForComparison("C:\\"));
42 expect(normalizeProjectPathForComparison("C:")).toBe(normalizeProjectPathForComparison("C:/"));
43 // Non-root drive paths keep their trailing separator trimmed as before.
44 expect(normalizeProjectPathForDispatch("C:\\repo\\")).toBe("C:\\repo");
45 });
3446});
packages/shared/src/path.ts+5-1
......@@ -22,7 +22,11 @@ export function isExplicitRelativePath(value: string): boolean {
2222}
2323
2424function isRootPath(value: string): boolean {
25 return value === "/" || value === "\\" || /^[a-zA-Z]:[/\\]?$/.test(value);
25 // The drive separator is required: a bare `C:` is not the drive root (it
26 // means "current directory on C:"), and treating it as already-canonical
27 // would leave it as `C:` while `C:\` and `C:/` normalize to the drive root,
28 // so the same location would fail project identity/dedup comparisons.
29 return value === "/" || value === "\\" || /^[a-zA-Z]:[/\\]$/.test(value);
2630}
2731
2832function trimTrailingPathSeparators(value: string): string {