[Fix] use path.sep in migrateWorkspace nested-path guard#548
[Fix] use path.sep in migrateWorkspace nested-path guard#548advancedresearcharray wants to merge 2 commits into
Conversation
On Windows, resolve() returns backslash paths so the hardcoded '/' prefix check never matched nested destinations. Use path.sep like ensureTaskDir already does in the same module. Closes clawwork-ai#382. Signed-off-by: advancedresearcharray <advancedresearcharray@users.noreply.github.com>
Address review feedback on clawwork-ai#510: normalize resolved paths before comparing on win32/darwin and use win32.sep for nested checks on Windows. Closes clawwork-ai#382. Signed-off-by: root <root@thirtynince.local> Co-authored-by: Cursor <cursoragent@cursor.com>
|
Hi @advancedresearcharray, DetailsInstructions for interacting with me using comments are available here. |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a critical issue in the workspace migration process where improper path validation on Windows could lead to recursive directory copying. By standardizing path separator usage and introducing case-insensitive checks, the fix ensures that destination paths are correctly validated against source paths regardless of the host operating system. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces the isNestedOrEqualWorkspacePath helper function to check if a target workspace path is nested within or equal to the current workspace path, accounting for platform-specific path separators and case-sensitivity. It also adds comprehensive unit tests. The review feedback identifies a critical bug where root directories (such as / or C:\) are not handled correctly, potentially causing recursive copy loops. To resolve this, the reviewer suggests refactoring the comparison logic using path.relative and path.isAbsolute and adding a corresponding test case.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| import { join, resolve, sep } from 'path'; | ||
| import { sep as winSep } from 'node:path/win32'; |
There was a problem hiding this comment.
To support robust path comparison using path.relative and path.isAbsolute (including platform-specific fallbacks for Windows to support testing on non-Windows platforms), let's import relative and isAbsolute from both 'path' and 'node:path/win32'.
| import { join, resolve, sep } from 'path'; | |
| import { sep as winSep } from 'node:path/win32'; | |
| import { join, resolve, sep, relative, isAbsolute } from 'path'; | |
| import { sep as winSep, relative as winRelative, isAbsolute as winIsAbsolute } from 'node:path/win32'; |
| export function isNestedOrEqualWorkspacePath(resolvedOld: string, resolvedNew: string): boolean { | ||
| const caseInsensitive = process.platform === 'win32' || process.platform === 'darwin'; | ||
| const normOld = caseInsensitive ? resolvedOld.toLowerCase() : resolvedOld; | ||
| const normNew = caseInsensitive ? resolvedNew.toLowerCase() : resolvedNew; | ||
| const pathSep = process.platform === 'win32' ? winSep : sep; | ||
| return normNew === normOld || normNew.startsWith(normOld + pathSep); | ||
| } |
There was a problem hiding this comment.
The current implementation of isNestedOrEqualWorkspacePath has a bug when the old workspace path is the root directory (e.g., / on Unix or C:\ on Windows).
Specifically, normOld + pathSep evaluates to // or C:\\. Since normNew (e.g., /workspace or C:\workspace) only has a single separator, normNew.startsWith(normOld + pathSep) evaluates to false. This allows a nested workspace migration from the root directory, which could trigger a recursive copy loop.
Using path.relative and path.isAbsolute is much more robust and automatically handles trailing separators, root directories, and other path normalization edge cases.
| export function isNestedOrEqualWorkspacePath(resolvedOld: string, resolvedNew: string): boolean { | |
| const caseInsensitive = process.platform === 'win32' || process.platform === 'darwin'; | |
| const normOld = caseInsensitive ? resolvedOld.toLowerCase() : resolvedOld; | |
| const normNew = caseInsensitive ? resolvedNew.toLowerCase() : resolvedNew; | |
| const pathSep = process.platform === 'win32' ? winSep : sep; | |
| return normNew === normOld || normNew.startsWith(normOld + pathSep); | |
| } | |
| export function isNestedOrEqualWorkspacePath(resolvedOld: string, resolvedNew: string): boolean { | |
| const caseInsensitive = process.platform === 'win32' || process.platform === 'darwin'; | |
| const normOld = caseInsensitive ? resolvedOld.toLowerCase() : resolvedOld; | |
| const normNew = caseInsensitive ? resolvedNew.toLowerCase() : resolvedNew; | |
| const rel = process.platform === 'win32' ? winRelative(normOld, normNew) : relative(normOld, normNew); | |
| const abs = process.platform === 'win32' ? winIsAbsolute(rel) : isAbsolute(rel); | |
| return !rel.startsWith('..') && !abs; | |
| } |
| }); | ||
| }); |
There was a problem hiding this comment.
|
All checks have been addressed and CI is green. This PR is ready for merge. |
|
CI is green, no review comments. Ready for merge. |
|
PR is ready for merge 🚀 |
|
The CI is green and there are no review comments. This PR is ready for merge. |
|
PR is ready for merge. CI is green and there are no outstanding review comments. |
|
Looks good, ready for merge! |
Closes #382
Summary
migrateWorkspaceused a hardcoded/when checking whether the new workspace path is inside the old one. On Windows,path.resolve()returns backslash paths, so the guard never fired and a recursive copy loop was possible.Uses
path.sep(same pattern asensureTaskDirin the same file). Also adds a case-insensitive path guard for Windows/macOS.Test plan
pnpm --filter @clawwork/desktop test(includesworkspace-init.test.ts)Release note