fix: handle Windows drive-letter paths across the codebase (#148)

* Add metrics collection and terminal performance tests

* fix: handle Windows drive-letter paths across the codebase

Windows paths like C:\Users\foo\project were broken in multiple places:
- agent-storage slugified D:\MyProject as D:-MyProject (illegal colon)
- terminal-manager rejected all non-/ paths as relative
- bootstrap parser misparsed drive colons as TCP host:port
- daemon/client connection helpers misclassified Windows paths
- CLI cwd filtering used hardcoded / separators
- checkout-git worktree detection used hardcoded / in path checks
- worktree archive used split("/").pop() instead of path.basename()

All path helpers now normalize separators and handle Windows
drive letters with case-insensitive comparison where needed.
This commit is contained in:
Mohamed Boudra
2026-03-27 18:17:48 +07:00
committed by GitHub
parent c667aca3e0
commit ca443b3ecf
25 changed files with 980 additions and 49 deletions
@@ -42,4 +42,12 @@ console.log("=== Local Daemon Utility Helpers ===\n");
console.log("✓ rejects empty and non-host listen values\n");
}
{
console.log("Test 5: rejects Windows absolute paths (not TCP endpoints)");
assert.strictEqual(resolveTcpHostFromListen("C:\\Users\\foo\\.paseo\\paseo.sock"), null);
assert.strictEqual(resolveTcpHostFromListen("D:\\project\\socket"), null);
assert.strictEqual(resolveTcpHostFromListen("C:\\paseo.sock"), null);
console.log("✓ rejects Windows absolute paths\n");
}
console.log("=== All local daemon utility tests passed ===");
@@ -41,6 +41,13 @@ console.log("=== CLI IPC Target Helpers ===\n");
console.log("✓ local unix socket paths normalize into IPC daemon targets\n");
}
{
console.log("Test 3b: Windows absolute paths are NOT treated as unix sockets");
assert.strictEqual(normalizeDaemonHost("C:\\Users\\foo\\.paseo\\paseo.sock"), null);
assert.strictEqual(normalizeDaemonHost("D:\\project\\socket"), null);
console.log("✓ Windows absolute paths are not treated as unix sockets\n");
}
{
console.log("Test 4: default host resolution tries local IPC first, then localhost fallback");
const paseoHome = mkdtempSync(path.join(os.tmpdir(), "paseo-client-targets-"));
@@ -0,0 +1,213 @@
#!/usr/bin/env npx tsx
/**
* CWD Filter Path Tests
*
* Tests that cwd filtering in agent commands works correctly
* with both Unix and Windows-style paths.
*
* Bug: The original code used hardcoded "/" separators for:
* 1. Stripping trailing separators (only stripped "/", not "\")
* 2. Descendant matching (appended "/" instead of platform separator)
*
* This causes Windows paths like "C:\Users\dev\project" to fail matching
* against "C:\Users\dev\project\sub" because the code appends "/" instead
* of "\" for the startsWith check.
*/
import assert from "node:assert";
import { isSameOrDescendantPath } from "../src/utils/paths.ts";
console.log("=== CWD Filter Path Tests ===\n");
// Test 1: Unix exact match
{
console.log("Test 1: Unix exact match");
assert.strictEqual(
isSameOrDescendantPath("/home/user/project", "/home/user/project"),
true,
"exact Unix paths should match",
);
console.log("✓ Unix exact match\n");
}
// Test 2: Unix descendant match
{
console.log("Test 2: Unix descendant match");
assert.strictEqual(
isSameOrDescendantPath("/home/user/project", "/home/user/project/src"),
true,
"Unix descendant should match",
);
console.log("✓ Unix descendant match\n");
}
// Test 3: Unix non-match (sibling)
{
console.log("Test 3: Unix non-match (sibling)");
assert.strictEqual(
isSameOrDescendantPath("/home/user/project", "/home/user/other"),
false,
"sibling directories should not match",
);
console.log("✓ Unix non-match (sibling)\n");
}
// Test 4: Unix prefix overlap (project vs project2)
{
console.log("Test 4: Unix prefix overlap (project vs project2)");
assert.strictEqual(
isSameOrDescendantPath("/home/user/project", "/home/user/project2"),
false,
"prefix overlap without separator should not match",
);
console.log("✓ Unix prefix overlap\n");
}
// Test 5: Unix trailing slash on base
{
console.log("Test 5: Unix trailing slash on base");
assert.strictEqual(
isSameOrDescendantPath("/home/user/project/", "/home/user/project/src"),
true,
"trailing slash on base should still match descendants",
);
console.log("✓ Unix trailing slash on base\n");
}
// Test 6: Unix trailing slash on candidate
{
console.log("Test 6: Unix trailing slash on candidate");
assert.strictEqual(
isSameOrDescendantPath("/home/user/project", "/home/user/project/"),
true,
"trailing slash on candidate should match as same dir",
);
console.log("✓ Unix trailing slash on candidate\n");
}
// Test 7: Windows exact match
{
console.log("Test 7: Windows exact match");
assert.strictEqual(
isSameOrDescendantPath("C:\\Users\\dev\\project", "C:\\Users\\dev\\project"),
true,
"exact Windows paths should match",
);
console.log("✓ Windows exact match\n");
}
// Test 8: Windows descendant match
{
console.log("Test 8: Windows descendant match");
assert.strictEqual(
isSameOrDescendantPath("C:\\Users\\dev\\project", "C:\\Users\\dev\\project\\src"),
true,
"Windows descendant should match",
);
console.log("✓ Windows descendant match\n");
}
// Test 9: Windows non-match (sibling)
{
console.log("Test 9: Windows non-match (sibling)");
assert.strictEqual(
isSameOrDescendantPath("C:\\Users\\dev\\project", "C:\\Users\\dev\\other"),
false,
"Windows sibling directories should not match",
);
console.log("✓ Windows non-match (sibling)\n");
}
// Test 10: Windows prefix overlap (project vs project2)
{
console.log("Test 10: Windows prefix overlap (project vs project2)");
assert.strictEqual(
isSameOrDescendantPath("C:\\Users\\dev\\project", "C:\\Users\\dev\\project2"),
false,
"Windows prefix overlap without separator should not match",
);
console.log("✓ Windows prefix overlap\n");
}
// Test 11: Windows trailing backslash on base
{
console.log("Test 11: Windows trailing backslash on base");
assert.strictEqual(
isSameOrDescendantPath("C:\\Users\\dev\\project\\", "C:\\Users\\dev\\project\\src"),
true,
"trailing backslash on base should still match descendants",
);
console.log("✓ Windows trailing backslash on base\n");
}
// Test 12: Windows trailing backslash on candidate
{
console.log("Test 12: Windows trailing backslash on candidate");
assert.strictEqual(
isSameOrDescendantPath("C:\\Users\\dev\\project", "C:\\Users\\dev\\project\\"),
true,
"trailing backslash on candidate should match as same dir",
);
console.log("✓ Windows trailing backslash on candidate\n");
}
// Test 13: Mixed separators (agent might use \ while CLI sends /)
{
console.log("Test 13: Mixed separators");
assert.strictEqual(
isSameOrDescendantPath("C:/Users/dev/project", "C:\\Users\\dev\\project\\src"),
true,
"mixed separators should still match",
);
console.log("✓ Mixed separators\n");
}
// Test 14: Deep Windows descendant
{
console.log("Test 14: Deep Windows descendant");
assert.strictEqual(
isSameOrDescendantPath(
"C:\\Users\\dev\\project",
"C:\\Users\\dev\\project\\src\\components\\Button.tsx",
),
true,
"deep Windows descendant should match",
);
console.log("✓ Deep Windows descendant\n");
}
// Test 15: Case-insensitive Windows descendant match
{
console.log("Test 15: Case-insensitive Windows descendant match");
assert.strictEqual(
isSameOrDescendantPath("C:\\Users\\Dev\\Project", "c:\\users\\dev\\project\\src"),
true,
"Windows paths with different casing should match (case-insensitive)",
);
console.log("✓ Case-insensitive Windows descendant match\n");
}
// Test 16: Case-insensitive Windows exact match
{
console.log("Test 16: Case-insensitive Windows exact match");
assert.strictEqual(
isSameOrDescendantPath("c:\\repo", "C:\\Repo"),
true,
"Windows paths with different casing should match exactly (case-insensitive)",
);
console.log("✓ Case-insensitive Windows exact match\n");
}
// Test 17: Parent should not match
{
console.log("Test 15: Parent should not match");
assert.strictEqual(
isSameOrDescendantPath("/home/user/project/src", "/home/user/project"),
false,
"parent directory should not match (only same-or-descendant)",
);
console.log("✓ Parent should not match\n");
}
console.log("=== All CWD filter path tests passed ===");