fix(cli): Handle ACP read_file for managed local paths#6021
Conversation
Allow ACP read_file calls to fall back to local reads for explicitly permitted local roots when the serve workspace boundary rejects them, and preserve useful messages for plain object read errors. Co-authored-by: Qwen-Coder <[email protected]>
|
Thanks for the PR, @doudouOUC! Template looks good ✓ On direction: Straightforward bug fix — ACP serve mode rejects On approach: The two concerns (ACP local read fallback + error normalization) are tightly coupled — the Moving on to code review. 🔍 中文说明感谢贡献,@doudouOUC! 模板完整 ✓ 方向:直接的 bug 修复 —— ACP serve 模式下拒绝了 方案:两个关注点(ACP 本地读取 fallback + 错误正规化)紧密耦合 —— 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewIndependent proposal: catch The PR's approach matches closely with two good additions: realpath resolution on both roots and files (prevents symlink-escape attacks), and No critical blockers. The security gating is tight: fallback requires both a recognized TestingUnit tests (all pass in PR worktree)
Typecheck: clean ✅ Real-scenario test (tmux)Test: read Before (installed qwen 0.19.1)Result: auto-allowed (path under After (this PR,
|
|
This is a clean, well-scoped bug fix. The author understood the problem precisely: ACP serve mode and The fallback logic is properly gated — requires both a recognized The tmux test confirmed the intentional behavioral change: 801 unit tests pass. Typecheck clean. The merge conflict resolution is clean and the resolve bot's duplicate test file warning was a false alarm (the PR already updated the existing test). Two non-blocking notes for the author:
Approved. ✅ 中文说明这是一个干净、范围明确的 bug 修复。作者精确理解了问题:ACP serve 模式和 fallback 逻辑有正确的门控 —— 需要同时识别 tmux 测试确认了有意的行为变更: 801 个单元测试通过。Typecheck 干净。合并冲突解决干净,resolve bot 的重复测试文件警告是误报(PR 已更新既有测试)。 两个不阻塞的备注:
批准 ✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
[Suggestion] writeTextFile does not normalize plain-object ACP errors. The normalizeError function is only applied in the readTextFile catch block. The writeTextFile method lets ACP errors propagate raw, so write operations that fail with a plain-object error will still surface [object Object] to downstream callers (e.g., write-file.ts uses String(error)). Consider wrapping the writeTextFile await in a try/catch with normalizeError, mirroring the read path.
— qwen3.7-max via Qwen Code /review
wenshao
left a comment
There was a problem hiding this comment.
[Suggestion] writeTextFile (unmodified in this PR) does not normalize errors the way readTextFile now does. ACP write errors may still surface as [object Object]. Consider adding a try/catch with normalizeError to writeTextFile for symmetry.
— qwen3.7-max via Qwen Code /review
Add the missing getUserAutoMemoryRoot export to the acpAgent test core mock so the updated acpAgent import resolves under Vitest. Co-authored-by: Qwen-Coder <[email protected]>
wenshao
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI still running.
— GPT-5 via Qwen Code /review
ytahdn
left a comment
There was a problem hiding this comment.
Cross-file note: the same instanceof Error ? error.message : String(error) pattern fixed in fileUtils.ts still exists in edit.ts:728 and write-file.ts:616-619. Since writeTextFile also doesn't normalize errors, write/edit operations can still surface [object Object] to users. Consider applying getErrorMessage() there too for defense in depth.
— qwen3.7-max via Qwen Code /review
ytahdn
left a comment
There was a problem hiding this comment.
LGTM! ✅
— qwen3.7-max via Qwen Code /review
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Remove the broad OS temp directory from default read_file allow roots and ACP local read fallback roots. Keep qwen-managed temp roots readable and reuse the shared isSubpath helper after realpath resolution for ACP fallback containment. Co-authored-by: Qwen-Coder <[email protected]>
Add the serve fast-path bundle check script and root npm script that the current CI workflow invokes. This mirrors the already-merged mainline check without pulling unrelated workflow or test config changes into this PR. Co-authored-by: Qwen-Coder <[email protected]>
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI still running.
— qwen3.7-max via Qwen Code /review
DragonnZhang
left a comment
There was a problem hiding this comment.
No additional findings at this head. The path_outside_workspace error-kind gating for local fallback, normalizeError in readTextFile, and the three new filesystem test cases (allowed root falls back, path outside configured root does not, empty configured roots ignored) all look correct. The writeTextFile error normalization gap (not wrapping in normalizeError symmetrically with readTextFile) and the same instanceof Error ? error.message : String(error) pattern in edit.ts:728 / write-file.ts:616-619 are already flagged in the existing reviews.
— claude-sonnet-4-6 via Qwen Code /review
Generated by Claude Code
Co-authored-by: Qwen-Coder <[email protected]>
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Co-authored-by: Qwen-Coder <[email protected]>
e2ffd48
Co-authored-by: Qwen-Coder <[email protected]>
|
@qwen-code /triage |
DragonnZhang
left a comment
There was a problem hiding this comment.
No new findings at HEAD 339cde1c8. The latest commits fully address the earlier Critical concerns: getLocalReadFallbackPath resolves both file and root paths via realpath() before isSubpath containment (symlink-safe), normalizeError creates a clean Error without copying untrusted properties, getErrorKind only trusts data.errorKind (not top-level), and the fallback read passes the resolved real path (no TOCTOU). The getErrorMessage plain-object branch and os.tmpdir() removal from read-file.ts allowedRoots are intentional behavioral changes documented in the PR body. Test coverage is thorough across symlink escape, missing files, error normalization, and root containment. — Qwen Code /review (automated)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
— qwen3.7-max via Qwen Code /review
|
|
||
| throw error; | ||
| const errorKind = getErrorKind(error); | ||
| const shouldTryLocalReadFallback = |
There was a problem hiding this comment.
[Suggestion] No test uses an ACP error with a valid data.errorKind string that is NOT in LOCAL_READ_FALLBACK_ERROR_KINDS (e.g., 'rate_limited'). The code path where getErrorKind() returns a defined string but isLocalReadFallbackErrorKind() returns false — meaning shouldTryLocalReadFallback is false and the error falls through to throw normalizeError(error) — is untested. A regression that broadens the fallback set would silently trigger local reads for unrelated errors.
Consider adding a test that rejects with { code: -32603, message: 'rate limited', data: { errorKind: 'rate_limited' } } and verifies no fallback is attempted.
— qwen3.7-max via Qwen Code /review
| @@ -689,9 +687,9 @@ The user has the ability to modify \`content\`. If modified, this will be stated | |||
| } | |||
| } | |||
| } catch (statError: unknown) { | |||
There was a problem hiding this comment.
[Suggestion] The lstatSync catch block was migrated to getErrorMessage(statError) but no test in write-file.test.ts triggers this path (no mock of fs.lstatSync throwing). The behavioral change is unverified — if lstatSync threw a plain-object error, the new getErrorMessage call would produce different output than the old inline check.
Consider adding a test that mocks fs.existsSync to return true and fs.lstatSync to throw a plain-object error.
— qwen3.7-max via Qwen Code /review
What this PR does
This PR makes ACP-backed
read_filepreserve the local read behavior that the tool already allows for managed local roots such as skill instructions, temp outputs, subagent transcripts, managed auto-memory, and user extensions. When the serve-side workspace boundary rejects one of those explicitly allowed local reads as outside the workspace, the agent falls back to the local file system service instead of surfacing the workspace-boundary rejection. It also improves error rendering so plain object failures surface their message or structured JSON instead of collapsing to[object Object].Why it's needed
Fixes #6020. A reported session loaded a user skill and then attempted to read instruction files under
/root/.qwen/skills/.... The skill directory is intentionally allowed byread_file, but underqwen servethe ACP file path went through a workspace-bound file system that rejected the same path as outside/workspace. The underlying structured error was then stringified as a plain object, leaving users and telemetry with onlyError reading file ...: [object Object]. Aligning ACP fallback with the tool's existing local read roots fixes the false failure while keeping reads outside those roots blocked, and the error-message normalization keeps future failures actionable.Reviewer Test Plan
How to verify
Run the focused ACP file system test and confirm it covers three outcomes: allowed local roots fall back to local reads, paths outside configured roots do not fall back, and empty configured roots are ignored. Run the focused core utility tests and confirm plain object read failures show the original message instead of
[object Object]. Run formatting, typecheck, and build; the expected result is no test/type/build failures, with only the existing VS Code companion curly lint warnings and existing Vite/Browserslist warnings during build.Evidence (Before & After)
Before: the captured session reported
Error reading file /root/.qwen/skills/dataworks-di-data-processor/instructions/interaction_norms.md: [object Object]witherror_type=read_content_failure. After: the regression tests confirm ACPpath_outside_workspaceerrors fall back only for explicitly allowed local roots, and plain object read errors render the usefulpath escapes workspace: ...message instead of[object Object].Tested on
Environment (optional)
Node.js v22.22.3, npm 10.9.8. Verification commands run locally:
cd packages/cli && npx vitest run src/acp-integration/service/filesystem.test.ts;cd packages/core && npx vitest run src/utils/fileUtils.test.ts src/utils/errors.test.ts src/tools/edit.test.ts;npx prettier --check packages/cli/src/acp-integration/acpAgent.ts packages/cli/src/acp-integration/service/filesystem.ts packages/cli/src/acp-integration/service/filesystem.test.ts packages/core/src/utils/errors.ts packages/core/src/utils/errors.test.ts packages/core/src/utils/fileUtils.ts packages/core/src/utils/fileUtils.test.ts;npm run typecheck;npm run build.Risk & Scope
read_fileshould allow; this is limited by requiring both the structuredpath_outside_workspaceerror and an explicit configured local read root match.qwen servesession against the original DataWorks skill environment; the validation is unit-level plus full typecheck/build. Windows and Linux were not tested locally.os.tmpdir(); those paths now follow the normalread_filepermission flow unless they are explicitly configured as local read roots. The fallback remains read-only and only applies after the ACP workspace boundary rejects a path that is already within configured local read roots.Linked Issues
Fixes #6020
中文说明
What this PR does
这个 PR 让 ACP 场景下的
read_file保持工具本身已经允许的本地读取行为,例如 skill instructions、临时输出、subagent transcript、托管 auto-memory 和用户 extensions。当 serve 侧 workspace 边界把这些显式允许的本地读取拒绝为 workspace 外路径时,agent 会回退到本地文件系统服务读取,而不是直接把 workspace 边界错误返回给用户。同时,这个 PR 改善了错误渲染逻辑,让普通对象错误展示其中的 message 或结构化 JSON,而不是退化成[object Object]。Why it's needed
修复 #6020。一次用户反馈的 session 先加载了用户 skill,随后读取
/root/.qwen/skills/...下的 instruction 文件。skill 目录本来被read_file明确允许,但在qwen serve下 ACP 文件路径走到了 workspace-bound 文件系统,被同一个路径判定为/workspace外部路径。随后这个结构化错误又被当作普通对象字符串化,导致用户和 telemetry 只能看到Error reading file ...: [object Object]。让 ACP fallback 和工具已有的本地读取根目录对齐,可以修复这个误失败,同时继续阻止这些根目录之外的读取;错误消息正规化也能让未来失败保留可诊断信息。Reviewer Test Plan
How to verify
运行聚焦的 ACP 文件系统测试,确认它覆盖三个结果:允许的本地 root 会回退到本地读取、配置 root 之外的路径不会回退、空 root 会被忽略。运行聚焦的 core utility 测试,确认普通对象读取失败会展示原始 message,而不是
[object Object]。运行格式检查、typecheck 和 build;预期没有测试、类型或构建失败,build 中只保留既有的 VS Code companion curly lint warnings 和既有的 Vite/Browserslist warnings。Evidence (Before & After)
修复前:捕获的 session 报告
Error reading file /root/.qwen/skills/dataworks-di-data-processor/instructions/interaction_norms.md: [object Object],并带有error_type=read_content_failure。修复后:回归测试确认 ACPpath_outside_workspace错误只会在显式允许的本地 root 内回退读取,并且普通对象读取错误会展示有用的path escapes workspace: ...message,而不是[object Object]。Tested on
Environment (optional)
Node.js v22.22.3,npm 10.9.8。本地运行的验证命令:
cd packages/cli && npx vitest run src/acp-integration/service/filesystem.test.ts;cd packages/core && npx vitest run src/utils/fileUtils.test.ts src/utils/errors.test.ts src/tools/edit.test.ts;npx prettier --check packages/cli/src/acp-integration/acpAgent.ts packages/cli/src/acp-integration/service/filesystem.ts packages/cli/src/acp-integration/service/filesystem.test.ts packages/core/src/utils/errors.ts packages/core/src/utils/errors.test.ts packages/core/src/utils/fileUtils.ts packages/core/src/utils/fileUtils.test.ts;npm run typecheck;npm run build。Risk & Scope
read_file的读取范围;当前通过同时要求结构化path_outside_workspace错误和显式配置的本地读取 root 命中来限制这个风险。qwen servesession;验证覆盖在单元测试、完整 typecheck 和 build 层面。Windows 和 Linux 没有在本地测试。os.tmpdir()下就被默认放行;除非这些路径被显式配置为本地读取 root,否则会走正常的read_file权限流程。fallback 仍然只读,并且只在 ACP workspace 边界拒绝一个已位于配置本地读取 root 内的路径后生效。Linked Issues
Fixes #6020