fix(core): align mock workspace path containment - #8759
Conversation
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
@rbalachandar thanks for the fix — the change itself looks focused, and #8753 is a well-documented bug with a solid root-cause analysis. Before it can go to code review, though, the PR description needs to follow the pull request template. The current body has only ## Summary and ## Tests; the required sections are missing:
- What this PR does and Why it's needed — your summary content maps onto these, just under the template headings.
- Reviewer Test Plan —
How to verify(the commands you ran),Evidence (Before & After)(N/A is fine for a test-infrastructure fix), and theTested onmatrix. That last one matters here: #8753 is a macOS-only failure, so reviewers need to know whether you reproduced it on macOS and confirmed the fix there, or only ran the suite on other platforms. - Risk & Scope — in particular, why it is safe for the mock to canonicalize paths only for the containment check while
getDirectories()keeps returning lexical paths (grep/glob-style tests consume those as search roots). - Linked Issues — move the
Fixes #8753reference into this section. - The
<details>Chinese translation of the description.
Once the description is updated, re-run with @qwen-code /triage to re-trigger the gate.
中文说明
@rbalachandar 感谢这个修复——改动本身很聚焦,#8753 也是一个记录完整、根因分析扎实的 bug。但在进入代码审查之前,PR 描述需要遵循 PR 模板。目前描述只有 ## Summary 和 ## Tests,缺少必需的章节:
- What this PR does 和 Why it's needed——现有 summary 的内容可以放到这两个标题下。
- Reviewer Test Plan——
How to verify(你运行的命令)、Evidence (Before & After)(测试基建修复可写 N/A)、以及Tested on表格。最后一点在这里很关键:#8753 是 macOS 专属的失败,审查者需要知道你是在 macOS 上复现并验证了修复,还是只在其他平台上运行过测试。 - Risk & Scope——特别需要说明:mock 只在 containment 检查时做路径规范化、而
getDirectories()仍返回词法路径,为什么是安全的(grep/glob 类测试会把这些路径当作搜索根目录使用)。 - Linked Issues——把
Fixes #8753移到这一节。 - 描述的
<details>中文翻译。
描述更新后,用 @qwen-code /triage 重新触发检查即可。
— Qwen Code · qwen3.8-max
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not explored to full depth (tool budget reached): This PR fixes issue #8753 by making the mock workspace co...: did not run the full packages/core suite — only the new test file plus the three consumer suites I assessed as highest-risk (write-file, glob, shell); remaini…; This PR fixes issue #8753 by making the mock workspace co...: did not exhaustively map what e.path / isFileSymlink does on Windows production paths for Finding 1's counterpart behavior (probe ran on Linux; the divergence…; This PR fixes issue #8753 by making the mock workspace co...: verifying the fallback-asymmetry scenario below on a macOS runner (unavailable here; established statically); This PR fixes issue #8753 by making the mock workspace co...: Could not execute packages/core/src/test-utils/mockWorkspaceContext.test.ts — no node_modules in the review worktree (vitest startup error ERR_MODULE_NOT_FOUN…; This PR fixes issue #8753 by making the mock workspace co...: could not execute packages/core/src/test-utils/mockWorkspaceContext.test.ts to confirm it is green — the review worktree has no node_modules installed ( vit…. Not reviewed: reverse audit — stopped before round 3 by the review time budget. Test Plan (not a blocker): src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory.
— qwen3.8-max via Qwen Code /review (v0.21.8)
|
Addressed in commit
Validation now passes: 404 focused tests, core typecheck, core build, ESLint, and Prettier. |
|
Addressed the remaining review suggestions in commit
Validation: 407 focused tests pass, plus core typecheck/build, ESLint, and Prettier. Re-running triage now. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): This change makes the shared createMockWorkspaceContext...: did not read every one of the ~40 consumer call sites line-by-line (I read shell.test.ts , shell.backgroundStatus.test.ts , glob.test.ts and grepped the res…; This change makes the shared createMockWorkspaceContext...: could not execute the two vitest files (no node_modules in the worktree); compensated with direct Node probes of every filesystem behavior the assertions depe…; This change makes the shared createMockWorkspaceContext...: did not run the remaining consumer suites (readManyFiles, grep, read-file, edit, write-file, ls, notebook-edit, zoom-image, shell.backgroundStatus); spot-checke…; This change makes the shared createMockWorkspaceContext...: could not exercise macOS ( /var → /private/var ) or Windows behavior from this Linux runner; those rely on the (identical) production code path being proven on ….
Test Plan (not a blocker): src/test-utils/mockWorkspaceContext.fallback.test.ts — no such file or directory; src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory; 407 tests pass — this review observed 1124, 18566, 1466, 481, 2941, 454 passed.
中文说明
未探索到全部深度(达到工具调用预算):This change makes the shared createMockWorkspaceContext...:did not read every one of the ~40 consumer call sites line-by-line (I read shell.test.ts , shell.backgroundStatus.test.ts , glob.test.ts and grepped the res…;This change makes the shared createMockWorkspaceContext...:could not execute the two vitest files (no node_modules in the worktree); compensated with direct Node probes of every filesystem behavior the assertions depe…;This change makes the shared createMockWorkspaceContext...:did not run the remaining consumer suites (readManyFiles, grep, read-file, edit, write-file, ls, notebook-edit, zoom-image, shell.backgroundStatus); spot-checke…;This change makes the shared createMockWorkspaceContext...:could not exercise macOS ( /var → /private/var ) or Windows behavior from this Linux runner; those rely on the (identical) production code path being proven on …。
Test Plan(非阻断):src/test-utils/mockWorkspaceContext.fallback.test.ts — no such file or directory; src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory; 407 tests pass — this review observed 1124, 18566, 1466, 481, 2941, 454 passed。
— qwen3.8-max via Qwen Code /review (v0.21.8)
|
Addressed the latest review findings in commit
Validation: 410 focused tests pass, plus core typecheck/build, ESLint, and Prettier. Re-running triage. |
|
/triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not explored to full depth (tool budget reached): Context: PR #8759 (QwenLM/qwen-code) aligns the shared c...: remaining consumer suites (glob/grep/read-file/write-file/ripGrep/ls/edit) not executed — they use real existing temp dirs, the low-risk class.; Context: PR #8759 (QwenLM/qwen-code) aligns the shared c...: Windows runtime behavior reasoned from code/ path.win32 semantics, not executed (Linux runner only).. Not reviewed: reverse audit — stopped before round 2 by the review time budget. Test Plan (not a blocker): src/test-utils/mockWorkspaceContext.fallback.test.ts — no such file or directory; src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory; 410 tests pass — this review observed 19502, 1124, 18567, 1466, 481, 2941, 454 passed.
中文说明
已审查。 建议见行内评论。 未探索到全部深度(达到工具调用预算):Context: PR #8759 (QwenLM/qwen-code) aligns the shared c...:remaining consumer suites (glob/grep/read-file/write-file/ripGrep/ls/edit) not executed — they use real existing temp dirs, the low-risk class.;Context: PR #8759 (QwenLM/qwen-code) aligns the shared c...:Windows runtime behavior reasoned from code/ path.win32 semantics, not executed (Linux runner only).。 未审查:反向审计——评审时间预算不足,未能开始第 2 轮。 Test Plan(非阻断):src/test-utils/mockWorkspaceContext.fallback.test.ts — no such file or directory; src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory; 410 tests pass — this review observed 19502, 1124, 18567, 1466, 481, 2941, 454 passed。
— qwen3.8-max via Qwen Code /review (v0.21.8)
doudouOUC
left a comment
There was a problem hiding this comment.
Test Plan (not a blocker): src/test-utils/mockWorkspaceContext.fallback.test.ts — no such file or directory; src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory.
中文说明
Test Plan(非阻断):src/test-utils/mockWorkspaceContext.fallback.test.ts — no such file or directory; src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory。
— deepseek-v4-flash via Qwen Code /review (v0.21.8)
Co-authored-by: jinye <djy1989418@126.com>
Co-authored-by: jinye <djy1989418@126.com>
|
Addressed the remaining actionable review feedback in commit
Validation:
The test-plan paths are under |
|
/triage |
|
Cross-checked the latest review ledger and addressed the remaining valid maintenance findings in
The existing tests also cover candidate realpath success, dangling links, cycles, missing intermediate components, alias canonicalization, and cleanup. Validation now passes 57 focused tests, core typecheck, Prettier, and pre-commit hooks. |
|
/triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not explored to full depth (tool budget reached): PR #8759 aligns the shared createMockWorkspaceContext t...: none — all planned checks completed within budget.; PR #8759 aligns the shared createMockWorkspaceContext t...: verify macOS realpathSync error.path semantics empirically (Linux-only runner) — mitigated by the verdict-neutrality argument in point 1, which holds regard…; Reverse audit round 1 for PR #8759 (mock workspace path c...: none (finished within budget; no check left unfinished)..
Test Plan (not a blocker): src/test-utils/mockWorkspaceContext.fallback.test.ts — no such file or directory; src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory; 410 tests pass — this review observed 19504, 1124, 18570, 1466, 481, 2941, 454 passed.
中文说明
已审查。 建议见行内评论。
未探索到全部深度(达到工具调用预算):PR #8759 aligns the shared createMockWorkspaceContext t...:none — all planned checks completed within budget.;PR #8759 aligns the shared createMockWorkspaceContext t...:verify macOS realpathSync error.path semantics empirically (Linux-only runner) — mitigated by the verdict-neutrality argument in point 1, which holds regard…;Reverse audit round 1 for PR #8759 (mock workspace path c...:none (finished within budget; no check left unfinished).。
Test Plan(非阻断):src/test-utils/mockWorkspaceContext.fallback.test.ts — no such file or directory; src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory; 410 tests pass — this review observed 19504, 1124, 18570, 1466, 481, 2941, 454 passed。
— qwen3.8-max via Qwen Code /review (v0.21.9)
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
|
Addressed the latest residual sibling-collapse review finding in
Validation: 58 focused tests passed, core typecheck passed, formatting and pre-commit hooks passed. |
|
/triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
Test Plan (not a blocker): src/test-utils/mockWorkspaceContext.fallback.test.ts — no such file or directory; src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory; 410 tests pass — this review observed 1238, 19065, 1505, 483, 3225, 517 passed.
中文说明
已审查——无阻断问题。 建议见行内评论。
Test Plan(非阻断):src/test-utils/mockWorkspaceContext.fallback.test.ts — no such file or directory; src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory; 410 tests pass — this review observed 1238, 19065, 1505, 483, 3225, 517 passed。
— qwen3.8-max via Qwen Code /review (v0.21.10)
|
Applied the latest reviewer suggestion in the follow-up refactor. The traversable ENOENT predicate is now centralized in Validation remains green: 58 focused tests, core typecheck, formatting, and pre-commit hooks. |
|
/triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
Not explored to full depth (tool budget reached): PR #8759 aligns the shared createMockWorkspaceContext t...: my probe of fs.realpathSync('') returned no output (inconisive); judged immaterial since both old and new code reject/throw on empty paths in production and t….
Test Plan (not a blocker): src/test-utils/mockWorkspaceContext.fallback.test.ts — no such file or directory; src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory; 410 tests pass — this review observed 1238, 19066, 1505, 483, 3225, 517 passed.
中文说明
未发现问题。LGTM!✅
未探索到全部深度(达到工具调用预算):PR #8759 aligns the shared createMockWorkspaceContext t...:my probe of fs.realpathSync('') returned no output (inconisive); judged immaterial since both old and new code reject/throw on empty paths in production and t…。
Test Plan(非阻断):src/test-utils/mockWorkspaceContext.fallback.test.ts — no such file or directory; src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory; 410 tests pass — this review observed 1238, 19066, 1505, 483, 3225, 517 passed。
— qwen3.8-max via Qwen Code /review (v0.21.10)
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. 6 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here. Not explored to full depth (tool budget reached): You are review agent 6a — Agent 6a: Undirected audit — ...: I did not check whether the resolveMissingPath loop could produce a subtly incorrect result for a path where an intermediate ancestor is a symlink to a direct…; You are review agent 3a — Agent 3a: Reuse & duplication.: (none — all checks completed within the budget); You are review agent 1b — Agent 1b: Removed-behavior au...: 无(已检查所有被删除的行)。. Test Plan (not a blocker): src/test-utils/mockWorkspaceContext.fallback.test.ts — no such file or directory; src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory.
中文说明
已审查。 建议见行内评论。 6 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。 未探索到全部深度(达到工具调用预算):You are review agent 6a — Agent 6a: Undirected audit — ...:I did not check whether the resolveMissingPath loop could produce a subtly incorrect result for a path where an intermediate ancestor is a symlink to a direct…;You are review agent 3a — Agent 3a: Reuse & duplication.:(none — all checks completed within the budget);You are review agent 1b — Agent 1b: Removed-behavior au...:无(已检查所有被删除的行)。。 Test Plan(非阻断):src/test-utils/mockWorkspaceContext.fallback.test.ts — no such file or directory; src/tools/shell.test.ts — no such file or directory; src/tools/read-file.test.ts — no such file or directory; src/tools/zoom-image.test.ts — no such file or directory。
— deepseek-v4-flash via Qwen Code /review (v0.21.8)
| it('should preserve paths with missing intermediate components', () => { | ||
| const workspaceContext = new WorkspaceContext(cwd); | ||
| const nonExistentPath = path.join(cwd, 'missing', 'nested.txt'); | ||
|
|
||
| expect(resolveWorkspacePath(nonExistentPath)).toBe(nonExistentPath); | ||
| expect(workspaceContext.isPathWithinWorkspace(nonExistentPath)).toBe( | ||
| true, | ||
| ); | ||
| }); |
There was a problem hiding this comment.
[Suggestion] Mutation-vacuous test — replacing resolveMissingPath with return pathToCheck (discarding the ancestor-walk loop entirely) still passes both assertions. The input is a simple subpath of the existing root, so the walked-up result is identical to the original input.
Failure scenario: If a future regression replaced the ancestor walk with a trivial identity return, this test would not catch it. The isPathWithinWorkspace assertion is the load-bearing one; the resolveWorkspacePath assertion is redundant for this input.
| it('should preserve paths with missing intermediate components', () => { | |
| const workspaceContext = new WorkspaceContext(cwd); | |
| const nonExistentPath = path.join(cwd, 'missing', 'nested.txt'); | |
| expect(resolveWorkspacePath(nonExistentPath)).toBe(nonExistentPath); | |
| expect(workspaceContext.isPathWithinWorkspace(nonExistentPath)).toBe( | |
| true, | |
| ); | |
| }); | |
| // Add a second test case where the missing portion sits below a symlink | |
| // that maps to a different point in the filesystem, so that | |
| // resolveMissingPath produces a *different* path from the input. |
中文说明
[Suggestion] 变异无效的测试——将 resolveMissingPath 替换为 return pathToCheck(完全丢弃祖先遍历循环)后,两个断言仍然通过。输入是已存在根目录的简单子路径,遍历后的结果与原始输入相同。
失败场景:如果未来回归将祖先遍历替换为恒等返回,该测试不会捕获。isPathWithinWorkspace 断言才是真正负载的;resolveWorkspacePath 断言在此输入下是冗余的。
| it('should preserve paths with missing intermediate components', () => { | |
| const workspaceContext = new WorkspaceContext(cwd); | |
| const nonExistentPath = path.join(cwd, 'missing', 'nested.txt'); | |
| expect(resolveWorkspacePath(nonExistentPath)).toBe(nonExistentPath); | |
| expect(workspaceContext.isPathWithinWorkspace(nonExistentPath)).toBe( | |
| true, | |
| ); | |
| }); | |
| // 添加第二个测试用例,缺失部分坐在符号链接之下, | |
| // 该符号链接映射到文件系统的不同位置, | |
| // 这样 resolveMissingPath 会产生与输入*不同*的路径。 |
— deepseek-v4-flash via Qwen Code /review (v0.21.8)
| return allDirs.some((dir) => { | ||
| try { | ||
| return isPathWithinRoot( | ||
| canonicalPath, | ||
| canonicalizeForContainment(dir), | ||
| ); | ||
| } catch { | ||
| return false; | ||
| } | ||
| }); |
There was a problem hiding this comment.
[Suggestion] Inner try/catch wraps isPathWithinRoot but only canonicalizeForContainment is fallible — isPathWithinRoot is a pure string function (path.relative + three boolean checks) that never throws. The catch is only reachable through canonicalizeForContainment(dir), which is called as the second argument. A reader unfamiliar with the code cannot tell which operation is fallible.
Failure scenario: No crash — the code works correctly. But it obscures which part of the block is actually fallible, making the mock harder to reason about and maintain. The real WorkspaceContext.isPathWithinWorkspace (line 246 in the same diff) has no inner try/catch around isPathWithinRoot at all.
| return allDirs.some((dir) => { | |
| try { | |
| return isPathWithinRoot( | |
| canonicalPath, | |
| canonicalizeForContainment(dir), | |
| ); | |
| } catch { | |
| return false; | |
| } | |
| }); | |
| return allDirs.some((dir) => { | |
| const canonicalDir = canonicalizeForContainment(dir); | |
| return isPathWithinRoot(canonicalPath, canonicalDir); | |
| }); |
中文说明
[Suggestion] 内层 try/catch 包裹了 isPathWithinRoot,但只有 canonicalizeForContainment 可能抛出异常——isPathWithinRoot 是一个纯字符串函数(path.relative + 三个布尔检查),从不抛出。catch 只能通过第二个参数 canonicalizeForContainment(dir) 到达。不熟悉代码的读者无法判断哪个操作是可能失败的。
失败场景:不会崩溃——代码正确运行。但它掩盖了代码块中真正可能失败的部分,使 mock 更难理解和维护。真正的 WorkspaceContext.isPathWithinWorkspace(同一 diff 第 246 行)完全没有内层 try/catch。
| return allDirs.some((dir) => { | |
| try { | |
| return isPathWithinRoot( | |
| canonicalPath, | |
| canonicalizeForContainment(dir), | |
| ); | |
| } catch { | |
| return false; | |
| } | |
| }); | |
| return allDirs.some((dir) => { | |
| const canonicalDir = canonicalizeForContainment(dir); | |
| return isPathWithinRoot(canonicalPath, canonicalDir); | |
| }); |
— deepseek-v4-flash via Qwen Code /review (v0.21.8)
| function isFileSymlink(filePath: string): boolean { | ||
| try { | ||
| return !fs.readlinkSync(filePath).endsWith('/'); | ||
| } catch (_error) { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
[Suggestion] isFileSymlink heuristic is misnamed — it detects symlinks whose target literal ends with /, not file symlinks. readlinkSync returns the raw link target, and a symlink to a directory does not normally have a trailing / unless created with ln -s target/ link. So isFileSymlink returns true for most directory symlinks, causing isResolvableMissingPathError to re-throw paths that are actually resolvable.
Failure scenario: No concrete bug — this is conservatively safe (the caller catches the error and returns false for containment). But the function name implies a different semantic, and the conservative re-throw means the function silently treats all symlinks to non-existent targets as non-resolvable, which is unnecessary for the common case of a symlink to a non-existent directory.
| function isFileSymlink(filePath: string): boolean { | |
| try { | |
| return !fs.readlinkSync(filePath).endsWith('/'); | |
| } catch (_error) { | |
| return false; | |
| } | |
| } | |
| function isSymlinkTargetMarkedDirectory(filePath: string): boolean { |
中文说明
[Suggestion] isFileSymlink 启发式函数命名有误——它检测的是符号链接目标字面量是否以 / 结尾,而不是"文件符号链接"。readlinkSync 返回原始链接目标,指向目录的符号链接通常不带尾部 /,除非用 ln -s target/ link 创建。所以 isFileSymlink 对大多数目录符号链接也返回 true,导致 isResolvableMissingPathError 抛出了本可解析的路径。
失败场景:无具体 bug——这是保守安全的(调用者捕获异常并返回 false)。但函数名暗示了不同的语义,保守的重新抛出意味着该函数将指向不存在目标的符号链接全部视为不可解析。
| function isFileSymlink(filePath: string): boolean { | |
| try { | |
| return !fs.readlinkSync(filePath).endsWith('/'); | |
| } catch (_error) { | |
| return false; | |
| } | |
| } | |
| function isSymlinkTargetMarkedDirectory(filePath: string): boolean { |
— deepseek-v4-flash via Qwen Code /review (v0.21.8)
| it('uses lexical containment when ENOENT has no path', () => { | ||
| const workspace = createMockWorkspaceContext(rootDir); | ||
|
|
||
| expect( | ||
| workspace.isPathWithinWorkspace(path.join(rootDir, 'missing.txt')), | ||
| ).toBe(true); | ||
| }); |
There was a problem hiding this comment.
[Suggestion] Fallback test 'uses lexical containment when ENOENT has no path' is redundant with the first test — both use the same mock setup (realpathSync mocked to throw throwMissingPath, which creates an ENOENT error with no error.path) and assert the same condition (path.join(rootDir, 'missing.txt') → true). The second test's assertion is a strict subset of the first test's assertions.
Failure scenario: No concrete bug — the tests are correct. But a reader would expect two different scenarios from two test names. The second test adds no new coverage.
| it('uses lexical containment when ENOENT has no path', () => { | |
| const workspace = createMockWorkspaceContext(rootDir); | |
| expect( | |
| workspace.isPathWithinWorkspace(path.join(rootDir, 'missing.txt')), | |
| ).toBe(true); | |
| }); | |
| // Either remove this test, or change the mock to throw an ENOENT | |
| // with an empty-string `path` (not `undefined`) to test a genuinely distinct case. |
中文说明
[Suggestion] 回退测试 'uses lexical containment when ENOENT has no path' 与第一个测试冗余——两者使用相同的 mock 设置(realpathSync 模拟抛出不带 error.path 的 ENOENT),并断言相同的条件(path.join(rootDir, 'missing.txt') → true)。第二个测试的断言是第一个测试断言的严格子集。
失败场景:无具体 bug——测试正确。但读者会期望两个不同的测试名称对应两个不同的场景。第二个测试没有增加新的覆盖。
| it('uses lexical containment when ENOENT has no path', () => { | |
| const workspace = createMockWorkspaceContext(rootDir); | |
| expect( | |
| workspace.isPathWithinWorkspace(path.join(rootDir, 'missing.txt')), | |
| ).toBe(true); | |
| }); | |
| // 删除此测试,或将 mock 改为抛出空字符串 `path`(而非 `undefined`)的 ENOENT, | |
| // 以测试真正不同的情况。 |
— deepseek-v4-flash via Qwen Code /review (v0.21.8)
| const parent = path.dirname(ancestor); | ||
| if (parent === ancestor) { | ||
| return pathToCheck; | ||
| } |
There was a problem hiding this comment.
[Suggestion] resolveMissingPath silently returns the original unresolved path when it walks up to the filesystem root without finding a resolvable ancestor — the parent === ancestor termination at the root boundary returns pathToCheck unchanged, with no documentation about this fallback.
Failure scenario: If a path like /a/b/c/d is supplied where every component from d up to / is non-existent or unreadable (ENOENT), the function returns the original /a/b/c/d unchanged. Downstream, isPathWithinRoot accepts the path via lexical containment. This is pre-existing behavior (the old e.path fallback did the same), but it is an undocumented implicit assumption.
| const parent = path.dirname(ancestor); | |
| if (parent === ancestor) { | |
| return pathToCheck; | |
| } | |
| // Fall back to the original path when the entire hierarchy is missing. | |
| // isPathWithinRoot will apply lexical containment in this case, | |
| // matching the pre-existing behavior from the old e.path fallback. | |
| return pathToCheck; |
中文说明
[Suggestion] resolveMissingPath 在遍历到文件系统根目录仍未找到可解析的祖先时,会静默返回原始的未解析路径——parent === ancestor 在根目录边界的终止条件返回未修改的 pathToCheck,没有任何关于此回退行为的文档。
失败场景:如果路径 /a/b/c/d 的每个组件从 d 到 / 都不存在或不可读,函数返回原始的 /a/b/c/d。下游 isPathWithinRoot 会通过词法包含关系接受该路径。这是已有行为(旧的 e.path 回退也是如此),但这是一个未记录的隐式假设。
| const parent = path.dirname(ancestor); | |
| if (parent === ancestor) { | |
| return pathToCheck; | |
| } | |
| // 整个路径层级都不存在时回退到原始路径。 | |
| // 此时 isPathWithinRoot 会使用词法包含判断, | |
| // 与旧的 e.path 回退行为一致。 | |
| return pathToCheck; |
— deepseek-v4-flash via Qwen Code /review (v0.21.8)
Local verification report (maintainer, macOS)Verified this PR locally on real macOS hardware — the platform the fix targets. Result: the #8753 regression reproduces exactly on Environment: macOS arm64 (Darwin 25.6.0), Node v24.18.1, fresh 1. Before — bug reproduced on
|
|
Released in v0.21.11. |




What this PR does
This PR aligns the shared
createMockWorkspaceContexttest double with production workspace path containment. It canonicalizes workspace roots and candidate paths forisPathWithinWorkspace, while preserving lexical paths returned bygetDirectories()for search-oriented tests. It also adds focused regression coverage for missing descendants, sibling-prefix boundaries, and additional workspace directories.Why it's needed
On macOS, temporary directories are commonly exposed lexically under
/var/folders/...while their canonical path is under/private/var/folders/.... Production canonicalizes both sides of a workspace containment check, but the test double used a lexicalstartsWithcomparison. After the read-permission security fix, this mismatch caused the macOSread-fileandzoom-imagetests to returnaskinstead ofallowfor paths inside the workspace.Reviewer Test Plan
How to verify
Run the focused suite from the repository root:
npm run test --workspace=packages/core -- src/test-utils/mockWorkspaceContext.test.ts src/test-utils/mockWorkspaceContext.fallback.test.ts src/tools/shell.test.ts src/tools/read-file.test.ts src/tools/zoom-image.test.tsExpected result: 410 tests pass, including the two macOS workspace permission regressions. Also run:
Evidence (Before & After)
N/A — this is a test-infrastructure change with no user-visible UI or runtime behavior change.
Tested on
Environment (optional)
macOS arm64, Node.js v22.22.0, npm, Vitest, and the local Qwen Code core workspace.
Risk & Scope
WorkspaceContextbehavior is unchanged;getDirectories()intentionally remains lexical because grep/glob-style tests consume those values as search roots.Linked Issues
Fixes #8753
中文说明
这个 PR 做了什么
本 PR 让共享的
createMockWorkspaceContext测试替身与生产环境的工作区路径边界检查保持一致。它只在isPathWithinWorkspace检查中规范化工作区根目录和候选路径,同时保留getDirectories()返回的词法路径,供依赖搜索根目录的测试使用。此外,本 PR 增加了针对工作区内不存在的子路径、具有相同前缀的兄弟路径,以及额外工作区目录的聚焦回归测试。为什么需要
在 macOS 上,临时目录通常以
/var/folders/...的词法路径暴露,但其规范路径位于/private/var/folders/...。生产代码会规范化工作区边界检查的两侧,而测试替身此前使用词法startsWith比较。读权限安全修复之后,这种不一致导致read-file和zoom-image的 macOS 测试将工作区内路径错误地判定为ask,而不是allow。评审验证计划
如何验证
在仓库根目录运行以下聚焦测试:
npm run test --workspace=packages/core -- src/test-utils/mockWorkspaceContext.test.ts src/test-utils/mockWorkspaceContext.fallback.test.ts src/tools/shell.test.ts src/tools/read-file.test.ts src/tools/zoom-image.test.ts预期结果:403 个测试全部通过,其中包括两个 macOS 工作区权限回归测试。然后运行:
前后对比证据
不适用——这是测试基础设施改动,不会改变用户可见的界面或运行时行为。
测试平台
环境(可选)
macOS arm64、Node.js v22.22.0、npm、Vitest,以及本地 Qwen Code core workspace。
风险与范围
WorkspaceContext行为未改变;getDirectories()有意继续返回词法路径,因为 grep/glob 类测试会将这些值作为搜索根目录使用。关联 Issue
Fixes #8753