fix(serve): normalize Windows path separators in workspace file read responses#4279
Conversation
…responses `workspaceRelative` returned the platform-native separator from `path.relative`, leaking backslashes into `/file`, `/stat`, `/list`, and `/glob` response paths on Windows. Surfaced as a Windows-only CI failure in the `GET /glob > scopes glob matches to cwd` test (`['sub\\inside.ts']` vs expected `['sub/inside.ts']`). Always emit POSIX-style separators so SDK consumers see the same shape across platforms. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
There was a problem hiding this comment.
Pull request overview
Normalizes Windows path separators in workspace file read response paths by converting path.sep to / in the workspaceRelative helper, fixing a Windows CI failure introduced in #4269.
Changes:
- Convert backslashes to forward slashes in
workspaceRelativereturn value on non-POSIX platforms. - Expand JSDoc to document the POSIX normalization contract for
/file,/stat,/list, and/globroutes.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
📋 Review SummaryThis PR fixes a Windows path separator leakage issue in the 🔍 General Feedback
🎯 Specific Feedback🟢 Medium
// Current implementation
return path.sep === '/' ? rel : rel.split(path.sep).join('/');
// Alternative that's more explicit about intent
return rel.split(path.sep).join('/');The 🔵 Low
return path.sep === '/' ? rel : rel.split(path.sep).join('/');
// Normalize to POSIX separators for consistent cross-platform API responsesThis helps future maintaineners understand this isn't just aesthetic — it's about API contract consistency.
✅ Highlights
|
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
wenshao
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅ — gpt-5.5 via Qwen Code /review
wenshao
left a comment
There was a problem hiding this comment.
The core fix (normalizing path.sep to / in workspaceRelative) is correct and minimal. A few observations on unchanged code (outside the PR diff, so noted here rather than inline):
requireStringQueryallows whitespace-only query strings (e.g.?path=%20%20) through, while rejecting empty strings — inconsistent with its JSDoc.clientId ?? undefinedis a no-op at 4 call sites after thenullguard already narrows the type tostring | undefined.trimmed.map((m) => workspaceRelative(req, m as string))uses an unnecessaryas stringcast that erases theResolvedPathbrand.
LGTM otherwise. ✅ — DeepSeek/deepseek-v4-pro via Qwen Code /review
| @@ -436,7 +441,8 @@ function workspaceRelative(req: Request, resolved: string): string { | |||
| throw new Error('bound workspace is not configured'); | |||
| } | |||
There was a problem hiding this comment.
[Suggestion] The normalization fix looks correct for standard Windows paths. Consider a defense-in-depth check: on Windows, if boundWorkspace and resolved are on different drives, path.relative returns the full absolute path (D:\other\file.txt) — which split/join would normalize to D:/other/file.txt, silently leaking an absolute path. The JSDoc promises "never fall back to returning absolute filesystem paths".
| } | |
| if (rel.startsWith('..') || path.isAbsolute(rel)) { | |
| throw new Error('resolved path escapes workspace'); | |
| } | |
| return path.sep === '/' ? rel : rel.split(path.sep).join('/'); |
— DeepSeek/deepseek-v4-pro via Qwen Code /review
Summary
Follow-up to PR #4269.
workspaceRelativeinpackages/cli/src/serve/routes/workspaceFileRead.tsreturned the platform-native separator frompath.relative, leaking backslashes into/file,/stat,/list, and/globresponse paths on Windows.Fix: always emit POSIX-style separators so SDK consumers see the same shape regardless of the daemon's host OS.
Why
CI for #4269 failed on
windows-latest, Node 22.x(run 26020995574):macOS / Ubuntu / Lint / CodeQL all passed — only Windows red, which is the classic signature of a non-normalized
path.sep. PR #4269 was already merged (commit 52d2850), so the bug currently sits onmain.The same helper backs
/file,/stat,/list, and/glob, so all four routes' responsepathfields would be affected on Windows; the existing tests only happened to exercise multi-segment paths via/glob.Test plan
pnpm vitest src/serve/routes/workspaceFileRead.test.ts— 28/28 pass on macOSpnpm vitest src/serve/— 595/595 pass (regression check across the serve module)pnpm eslint packages/cli/src/serve/routes/workspaceFileRead.ts— clean'scopes glob matches to cwd'case acts as the regression test🤖 Generated with Qwen Code