Skip to content

fix(core): allow reading saved plan files without a confirmation prompt#7678

Merged
wenshao merged 1 commit into
QwenLM:mainfrom
zjunothing:fix/plans-dir-read-root
Jul 25, 2026
Merged

fix(core): allow reading saved plan files without a confirmation prompt#7678
wenshao merged 1 commit into
QwenLM:mainfrom
zjunothing:fix/plans-dir-read-root

Conversation

@zjunothing

Copy link
Copy Markdown
Collaborator

What this PR does

Adds the plans directory (config.getPlansDir(), default ~/.qwen/plans) to the two mirrored permission-free read-root lists: ReadFileTool.getDefaultPermission()'s allowedRoots and AcpAgent.buildAcpLocalReadRoots() (per the SYNC comment binding the two). Reading a saved plan file no longer lands on an ask confirmation; everything else under ~/.qwen (settings.json, credentials) stays confirmation-gated, pinned by a new test.

Why it's needed

This is finding #3 from the #7197 review (review comment), which asked for exactly this change and noted it "should land before this trade is considered complete": the approved-plan pointer replaces the in-history plan text with a reference to the saved file, but the default plans dir sits outside the workspace and in none of ReadFileTool's permission-free roots — so the pointer's own instruction ("read that file if you need to consult it again") stalls on an ask prompt, popped at exactly the moment the user just approved the plan and told the agent to start coding; in non-interactive/ACP flows an ask can resolve to a denial. Since nothing re-injects the plan after approval, the saved file is the model's only recovery route for the plan text. The change is independently safe to land: plan files are already written today by savePlan at approval, and reading them back should never have required a prompt.

Reviewer Test Plan

How to verify

  1. In a session with an approved plan (plan file saved under ~/.qwen/plans), have the model read_file that path. Before: an ask confirmation pops (or the read is denied in ACP). After: the read proceeds directly. Reading ~/.qwen/settings.json still prompts.
  2. npx vitest run src/tools/read-file.test.ts (packages/core, 87/87) and npx vitest run src/acp-integration/acpAgent.test.ts (packages/cli, 317/317).

Evidence (Before & After)

E2E drives the compiled Config + ReadFileTool with a real plan file written to config.getPlanFilePath() under a temp QWEN_HOME, before/after via git stash + rebuild:

before/after E2E + tests

check unpatched (origin/main) patched
saved plan file permission ask allow
~/.qwen/settings.json stays gated (ask) ✅ (unchanged, now pinned)
workspace file stays allow ✅ (unchanged)
new unit test: allow for plan files
ACP fallback read roots include the plans dir (3 existing tests re-pinned)

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

macOS (Darwin 24.6), Node v22.23.1; vitest. npm run typecheck, eslint --max-warnings 0, prettier --check all clean.

Risk & Scope

Linked Issues

Refs #6237 (follow-up to the #7197 review, finding #3)

中文说明

本 PR 做了什么

把 plans 目录(config.getPlansDir(),默认 ~/.qwen/plans)加入两处互为镜像的免确认读根列表:ReadFileTool.getDefaultPermission()allowedRootsAcpAgent.buildAcpLocalReadRoots()(两处由 SYNC 注释绑定)。读取已保存的 plan 文件不再弹 ask 确认;~/.qwen 下的其它文件(settings.json、凭据)仍保持确认门控,并由新测试钉住。

为什么需要

这是 #7197 review 的 finding #3(review 明确要求本改动并指出"应在认定该取舍完成之前落地"):approved-plan pointer 把历史中的 plan 原文换成对已保存文件的引用,但默认 plans 目录在工作区之外、也不在任何免确认读根里——pointer 自带的"需要时可重读该文件"指令会撞上 ask 确认框,恰好出现在用户刚批准 plan、让 agent 开始写码的时刻;非交互/ACP 流程中 ask 还可能解析为拒绝。批准后没有任何机制重新注入 plan,已保存文件是模型找回 plan 文本的唯一途径。本改动可独立安全落地:plan 文件今天就由批准时的 savePlan 写入,读回它本不该需要确认。

审阅测试计划

如何验证

  1. 在有已批准 plan 的会话中让模型 read_file 该路径:之前弹确认(ACP 下可能被拒);之后直接读取。读 ~/.qwen/settings.json 仍会弹确认。
  2. packages/core 下 read-file 87/87;packages/cli 下 acpAgent 317/317。

证据(Before & After)

E2E 驱动编译产物 Config + ReadFileTool,在临时 QWEN_HOME 下用 config.getPlanFilePath() 写真实 plan 文件,git stash + 重建对照:未修复时 plan 文件是 ask;修复后 allow,settings.json 与工作区文件行为不变。新单测在未修复源码上失败;ACP 读根 3 条既有测试重新钉住。

测试平台

macOS 已本地验证(✅);Windows / Linux 依赖 CI(⚠️)。

环境

macOS(Darwin 24.6)、Node v22.23.1;vitest;typecheck / eslint / prettier 全绿。

风险与范围

关联 Issue

Refs #6237#7197 review finding #3 的 follow-up)

🤖 Generated with Claude Code

The default plans dir (~/.qwen/plans) sits outside the workspace and was
in none of ReadFileTool's permission-free roots, so reading a saved plan
back landed on an ask confirmation — popped at exactly the moment the
user approved the plan and told the agent to start coding, and resolvable
as a denial in non-interactive/ACP flows. With the approved-plan pointer
(QwenLM#7197) the saved file is the model's only recovery route for the plan
text, so the read must not stall on a prompt.

Adds config.getPlansDir() to ReadFileTool.getDefaultPermission's
allowedRoots and to AcpAgent.buildAcpLocalReadRoots (per the SYNC
comment). The dir holds only session plan files; sibling ~/.qwen files
such as settings.json stay confirmation-gated, pinned by a new test.

Refs QwenLM#6237

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments above for the result.

Qwen Triage 已完成 —— 查看运行。结果见上方各阶段评论。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: observed and well-documented. This is finding #3 from the #7197 review — the approved-plan pointer tells the model to re-read the saved plan file, but ~/.qwen/plans sits outside the workspace and in none of ReadFileTool's permission-free roots, so the read stalls on an ask prompt (or gets denied outright in ACP/non-interactive flows). The #7197 comment thread confirms this was explicitly requested as a follow-up.

Direction: aligned. This closes a gap in the permission model that the #7197 review identified before that trade could be considered complete. Plan files are already written today by savePlan at approval; reading them back should never have required a prompt.

Size: 9 production lines (6 in read-file.ts, 3 in acpAgent.ts), 21 test lines. Well under any threshold.

Approach: the scope is exactly right — add config.getPlansDir() to the two mirrored read-root lists (per the SYNC comment binding them), with tests pinning both the new allow behavior and the security boundary (settings.json stays gated). Nothing to cut.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:已观测且有充分记录。这是 #7197 review 的 finding #3——approved-plan pointer 让模型重读已保存的 plan 文件,但 ~/.qwen/plans 在工作区之外、也不在任何免确认读根里,读取会撞上 ask 确认(ACP/非交互流程中可能直接被拒)。#7197 评论区确认这是明确要求的 follow-up。

方向:对齐。这修补了 #7197 review 在该取舍认定完成之前识别出的权限模型缺口。plan 文件今天就由批准时的 savePlan 写入,读回它本不该需要确认。

规模:9 行生产代码(read-file.ts 6 行,acpAgent.ts 3 行),21 行测试。远低于任何阈值。

方案:范围恰好——按 SYNC 注释的绑定关系,把 config.getPlansDir() 加入两处镜像读根列表,测试同时钉住新的 allow 行为和安全边界(settings.json 保持门控)。无需删减。

进入代码审查 🔍

Qwen Code · qwen3.8-max-preview

Reviewed at 1f5c0e18d8530fe6ea18a28e0b2b6ecf6c619a9c · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal: given the problem (plan files under ~/.qwen/plans trigger an ask confirmation on read, stalling the model's only recovery route for approved plan text), I would add config.getPlansDir() to the allowedRoots in ReadFileTool.getDefaultPermission() and to buildAcpLocalReadRoots() in acpAgent.ts (the SYNC comment binds the two), then pin both the new allow and the security boundary (settings.json stays gated) with tests.

Comparison: the PR does exactly this. The two production edits land in the correct position in each list — after Storage.getUserExtensionsDir(), before the ACP-only fallback roots — preserving the SYNC alignment. The comment in read-file.ts explains the non-obvious why (plan files are the model's only recovery route; the dir holds only session plan files, never credentials). The acpAgent.ts comment cross-references the SYNC binding. Both are appropriate.

The test additions are well-structured: the read-file.test.ts mock gains getPlansDir returning ~/.qwen/plans, and two new cases pin (1) plan files under that dir get allow and (2) ~/.qwen/settings.json still gets ask. The acpAgent.test.ts mock and expected read-roots array are updated to include the plans dir, re-pinning the three existing ACP tests.

No correctness bugs, security holes, or regressions found. The security model is sound: Storage.getPlansDir() enforces that a configured plansDirectory must resolve within the project root (already workspace-readable), so the permission-free widening is effectively the default ~/.qwen/plans only.

No findings. ✅

CI Test Evidence

At review time (1f5c0e18):

Check Status
precheck-pr / precheck ✅ success
Classify PR ✅ success
label ✅ success
review-config ✅ success
authorize ✅ success
Test (ubuntu-latest, Node 22.x) ⏳ still running
Test (macos-latest, Node 22.x) skipped (fork)
Test (windows-latest, Node 22.x) skipped (fork)
Integration Tests (CLI, No Sandbox) skipped (fork)

The main ubuntu test suite is still in progress at review time — no failures so far. The author reports 87/87 on read-file.test.ts and 317/317 on acpAgent.test.ts locally; typecheck, eslint, and prettier clean.

This PR touches the read_file permission model (user-visible confirmation behavior). A maintainer can trigger the isolated @qwen-code /tmux job for real-scenario coverage if desired.

中文说明

代码审查

独立方案: 针对该问题(~/.qwen/plans 下的 plan 文件读取触发 ask 确认,阻断模型找回已批准 plan 文本的唯一途径),我会把 config.getPlansDir() 加入 ReadFileTool.getDefaultPermission()allowedRootsacpAgent.tsbuildAcpLocalReadRoots()(SYNC 注释绑定两处),然后用测试钉住新的 allow 行为和安全边界(settings.json 保持门控)。

对比: PR 完全这样做了。两处生产编辑落在各列表的正确位置——Storage.getUserExtensionsDir() 之后、ACP 专属回退根之前——保持了 SYNC 对齐。read-file.ts 中的注释解释了非显而易见的原因(plan 文件是模型的唯一恢复途径;该目录只含会话 plan 文件,不含凭据)。acpAgent.ts 的注释交叉引用了 SYNC 绑定。两者都恰当。

测试新增结构良好:read-file.test.ts mock 增加 getPlansDir 返回 ~/.qwen/plans,两个新用例分别钉住 (1) 该目录下的 plan 文件获得 allow,(2) ~/.qwen/settings.json 仍为 askacpAgent.test.ts 的 mock 和预期读根数组同步更新,重新钉住 3 条既有 ACP 测试。

未发现正确性 bug、安全漏洞或回归。安全模型可靠:Storage.getPlansDir() 强制配置的 plansDirectory 必须解析在项目根内(本就工作区可读),所以免确认面的实际扩大仅为默认的 ~/.qwen/plans

无发现 ✅

CI 测试证据

审查时(1f5c0e18):precheck / Classify PR / label / review-config / authorize 均 ✅;ubuntu 主测试套件仍在运行中,暂无失败。macOS / Windows / 集成测试因 fork 跳过。作者本地报告 read-file 87/87、acpAgent 317/317,typecheck / eslint / prettier 全绿。

本 PR 触及 read_file 权限模型(用户可见的确认行为)。如需真实场景覆盖,维护者可触发隔离的 @qwen-code /tmux 任务。

Qwen Code · qwen3.8-max-preview

Reviewed at 1f5c0e18d8530fe6ea18a28e0b2b6ecf6c619a9c · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 5/5 — clean across every stage; minimal, correct fix for a confirmed gap in the permission model.

This is exactly the follow-up the #7197 review asked for. Nine production lines that add config.getPlansDir() to the two mirrored read-root lists, with tests pinning both the new behavior and the security boundary. The SYNC alignment is correct, the comments explain the non-obvious why, and the security model holds — a configured plansDirectory must resolve within the project root, so the widening is effectively ~/.qwen/plans only. Nothing to cut, nothing missing. Ships the fix cleanly.

中文说明

置信度:5/5——各阶段均干净;以最小、正确的改动修补了权限模型中已确认的缺口。

这正是 #7197 review 要求的 follow-up。9 行生产代码把 config.getPlansDir() 加入两处镜像读根列表,测试同时钉住新行为和安全边界。SYNC 对齐正确,注释解释了非显而易见的原因,安全模型成立——配置的 plansDirectory 必须解析在项目根内,实际扩大仅为 ~/.qwen/plans。无需删减,无遗漏。干净地交付了修复。

Qwen Code · qwen3.8-max-preview

Reviewed at 1f5c0e18d8530fe6ea18a28e0b2b6ecf6c619a9c · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@wenshao

wenshao commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Maintainer local verification — merge reference

I built and ran this PR locally in an isolated worktree against a real compiled dist, with an A/B diff against origin/main (merge-base 9e7acec8). Everything checks out; the fix does exactly what it claims and nothing more.

Environment: macOS (Darwin 24.6) · Node v22.23.1 · vitest v3.2.4

1. Focused suites (PR head) ✅

suite result
packages/coresrc/tools/read-file.test.ts 87 passed (87)
packages/clisrc/acp-integration/acpAgent.test.ts 317 passed (317)

Matches the numbers in the PR body exactly.

2. A/B load-bearing check ✅ (are the new tests real?)

Reverted only the two source files to origin/main, kept the new/updated tests, rebuilt, re-ran:

suite on head source reverted
core read-file.test.ts 87/87 1 failedshould return allow for saved plan files under the plans directory
cli acpAgent.test.ts 317/317 3 failed — all three ACP fallback-read-root tests

Crucially, the scope-guard test should still return ask for ~/.qwen files outside the plans directory stays green on the reverted source — so the assertions genuinely pin the new behavior (allow for plan files) rather than trivially passing.

3. No-mock real-run E2E (before/after) ✅

Drove a real Config + ReadFileTool (compiled dist), wrote a real plan file at config.getPlanFilePath() under a temp QWEN_HOME, and read back the actual getDefaultPermission() decision for five path classes:

read_file target BEFORE (origin/main) AFTER (PR 7678)
saved plan file ~/.qwen/plans/<session>.md ask ✗ (stalls) allow ← the fix
~/.qwen/settings.json (sibling) ask ask ✓ (unchanged, now pinned)
~/.qwen/plans-secret/x.md (prefix sibling) ask ask ✓ (boundary-safe)
workspace file allow allow
unrelated outside file ask ask

Exactly one cell flips (ask → allow for the plan file). Every sibling / outside path is byte-identical → the permission widening is surgical. I added an adversarial plans-secret prefix-sibling case; it correctly stays ask because isSubpath() uses a path.relative boundary check (not a string prefix), so no sibling under ~/.qwen leaks in.

4. Scope / hygiene ✅

  • The getPlansDir() widening covers only ~/.qwen/plans. When plansDirectory is configured, Config asserts it resolves within the project root (already workspace-readable), so the effective new surface is the default ~/.qwen/plans only — matches the Risk & Scope section.
  • prettier --check, eslint --max-warnings 0, and tsc --noEmit (core) all clean on the 4 changed files.
  • Upstream CI on this PR is already green (Test / Real daemon E2E / triage).

Evidence

PR 7678 local verification

Verdict: verified — the change is minimal, correct, load-bearing, and narrowly scoped. Good to merge from a verification standpoint. 👍

中文版本(点击展开)

维护者本地验证 —— 合并参考

我在隔离 worktree 中用真实编译产物 dist 本地构建并运行了本 PR,并与 origin/main(merge-base 9e7acec8)做了 A/B 对照。结论:改动完全符合描述,且不多做一分。

环境: macOS(Darwin 24.6)· Node v22.23.1 · vitest v3.2.4

1. 定向测试(PR head)✅

  • packages/coreread-file.test.ts87/87 通过
  • packages/cliacpAgent.test.ts317/317 通过

与 PR 描述中的数字完全一致。

2. A/B「测试是否载荷」检查 ✅

只把两个源码文件回退到 origin/main、保留新增/修改的测试后重建重跑:

  • core read-file.test.ts1 个失败 —— should return allow for saved plan files under the plans directory
  • cli acpAgent.test.ts3 个失败 —— 三个 ACP fallback 读根测试全红

关键点:作用域护栏测试 should still return ask for ~/.qwen files outside the plans directory 在回退源码后依然为绿——说明这些断言真正钉住的是新行为(plan 文件 allow),而非平凡通过。

3. 无 mock 真实运行 E2E(前后对照)✅

驱动真实 Config + ReadFileTool(编译产物),在临时 QWEN_HOME 下用 config.getPlanFilePath() 写入真实 plan 文件,读取真实的 getDefaultPermission() 决策:

read_file 目标 之前(origin/main 之后(PR 7678)
已保存 plan 文件 ~/.qwen/plans/<session>.md ask ✗(卡确认) allow ← 修复点
~/.qwen/settings.json(同级) ask ask ✓(不变,现被钉住)
~/.qwen/plans-secret/x.md(前缀同级) ask ask ✓(边界安全)
工作区文件 allow allow
无关外部文件 ask ask

只有一个单元格翻转(plan 文件 ask → allow),其余同级/外部路径逐字节不变 →免确认读面扩大是「外科手术式」的。我额外加了对抗用例 plans-secret 前缀同级目录,它正确保持 ask,因为 isSubpath() 用的是 path.relative 边界判断(而非字符串前缀),~/.qwen 下任何同级都不会漏进来。

4. 范围 / 卫生 ✅

  • getPlansDir() 只扩大 ~/.qwen/plans。配置 plansDirectoryConfig 会断言其解析在项目根内(本就工作区可读),因此实际新增读面仅默认的 ~/.qwen/plans——与「风险与范围」一致。
  • 4 个改动文件的 prettier --checkeslint --max-warnings 0tsc --noEmit(core)全绿。
  • 该 PR 的上游 CI 已全绿(Test / Real daemon E2E / triage)。

结论: 已验证——改动最小、正确、载荷、范围收敛。从验证角度可以合并。👍

Verification run locally by the maintainer against the PR head (1f5c0e18). Evidence image hosted on the repo's pr-assets/7678-verify branch.

@zjunothing

Copy link
Copy Markdown
Collaborator Author

Thanks for the verification — the adversarial plans-secret sibling-prefix case is a great addition; good to have it confirmed that isSubpath's path.relative boundary keeps every ~/.qwen sibling gated. 中文:感谢验证——plans-secret 前缀同级的对抗用例补得很好,确认了边界判断不会放过任何同级目录。

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found. LGTM! ✅

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Maintainer re-verification (round 2) — re-run against today's main

Following up on my round-1 verification. The PR head has not moved (1f5c0e18d), but main has advanced 11 commits since that round's merge-base, so I rebuilt and re-ran everything from scratch against the current tip to confirm the result still holds at merge time.

round 1 round 2 (this run)
PR head 1f5c0e18d 1f5c0e18d (unchanged)
base merge-base 9e7acec8 main tip 451bcad9 (+11 commits)
verified revision merge result 7adb1827

Environment: macOS (Darwin 24.6) · Node v22.23.1 · vitest 3.2.4 · isolated worktree, real compiled dist.

1. Merge is clean, diff unchanged ✅

None of the 11 new commits touch read-file.ts / acpAgent.ts (or config.ts / paths.ts / storage.ts). Merging the PR into main produces no conflicts, and the merged-state diff is byte-for-byte the PR's diff — 4 files, 30 insertions, 0 deletions. No silent semantic drift from the base advance.

2. Focused suites at the new base ✅

suite result
packages/coresrc/tools/read-file.test.ts 87 passed (87)
packages/clisrc/acp-integration/acpAgent.test.ts 317 passed (317)

Both reproduce the round-1 numbers and the PR body exactly.

3. A/B load-bearing check ✅

Reverted only the two source files to origin/main, kept the PR's tests, rebuilt, re-ran:

suite on head source reverted
core read-file.test.ts 87/87 1 failedshould return allow for saved plan files under the plans directoryexpected 'ask' to be 'allow'
cli acpAgent.test.ts 317/317 3 failed — all three ACP fallback-read-root tests

The scope-guard test should still return ask for ~/.qwen files outside the plans directory stays green on the reverted source. That is the discriminator worth noting: it means the new assertions pin the new behaviour rather than being trivially satisfied, and the settings.json guard is a genuine regression pin (base already gated it) rather than a test written to match whatever the code happened to do.

4. No-mock real-run E2E ✅

Drove a real Config + ReadFileTool from the compiled dist — no vitest, no mocked config object (the shipped unit test stubs getPlansDir, so this exercises the real Config/Storage path instead). The plan file is written by the product's own config.savePlan(), i.e. the exact code that runs when a user approves a plan, under a temp QWEN_HOME:

read_file target BEFORE (origin/main) AFTER (PR 7678)
saved plan file ~/.qwen/plans/<session>.md ask ✗ (stalls) allow ← the fix
~/.qwen/settings.json ask ask ✓ (unchanged)
~/.qwen/plans-secret/x.md (prefix sibling) ask ask ✓ (boundary-safe)
workspace file allow allow
unrelated outside file ask ask

diff of the two full runs is two lines — exactly one permission cell moves. The prefix-sibling case stays ask because isSubpath() uses a path.relative boundary check rather than a string prefix.

5. New this round — can a configured plansDirectory escape the project root? 🔍

The PR's Risk & Scope section claims the widening is effectively the default ~/.qwen/plans only, because a configured plansDirectory must resolve within the project root. Round 1 took that from reading the code; this round I probed it against the real Config:

configured plansDirectory outcome
"plans" (inside root) allowed — and already workspace-readable pre-PR, so no new surface
"../escape" rejectedFatalConfigError
"/etc" rejectedFatalConfigError
"~/.ssh" rejectedFatalConfigError

No configured value escapes the project root, so the newly permission-free surface really is just the default <QWEN_HOME>/plans. The Risk & Scope claim is accurate as written.

6. Gates on the 4 changed files ✅

prettier --check clean · eslint --max-warnings 0 exit 0 · tsc --noEmit clean for both packages/core and packages/cli (round 1 only covered core). Upstream CI on this PR is green.

Evidence

PR 7678 round-2 verification

Verdict: re-verified at today's main. Merges clean, every round-1 result reproduces, the permission widening is exactly one path class, and the stated scope limit is now empirically confirmed rather than inferred. No blockers — good to merge from a verification standpoint. 👍

中文版本(点击展开)

维护者复验(第 2 轮)—— 基于今天的 main 重跑

这是对我第 1 轮验证的跟进。PR head 没有变动1f5c0e18d),但 main 相对该轮 merge-base 已前进 11 个提交,因此我在当前 tip 上从零重新构建并重跑了全部验证,确认结论在合并时点依然成立。

第 1 轮 第 2 轮(本次)
PR head 1f5c0e18d 1f5c0e18d(未变)
base merge-base 9e7acec8 main tip 451bcad9(+11 提交)
实际验证的修订 合并结果 7adb1827

环境: macOS(Darwin 24.6)· Node v22.23.1 · vitest 3.2.4 · 隔离 worktree、真实编译产物 dist

1. 合并干净,diff 未漂移 ✅

11 个新提交没有触碰 read-file.ts / acpAgent.ts(也没碰 config.ts / paths.ts / storage.ts)。把 PR 合入 main 无冲突,合并后相对 main 的 diff 与 PR 自身 diff 完全一致——4 文件、30 行新增、0 删除,base 前进没有引入任何语义漂移。

2. 新 base 下的定向测试 ✅

测试 结果
packages/coresrc/tools/read-file.test.ts 87/87 通过
packages/clisrc/acp-integration/acpAgent.test.ts 317/317 通过

与第 1 轮及 PR 描述中的数字完全一致。

3. A/B 承重性检查 ✅

把两个源文件回退到 origin/main,保留 PR 的测试,重新构建后重跑:

测试 PR head 源码回退后
core read-file.test.ts 87/87 1 条失败 —— should return allow for saved plan files under the plans directoryexpected 'ask' to be 'allow'
cli acpAgent.test.ts 317/317 3 条失败 —— 三条 ACP 兜底读根测试全部失败

关键判别点:作用域守卫测试 should still return ask for ~/.qwen files outside the plans directory 在回退源码上依然通过。这说明新断言钉住的是新行为,而不是恰好为真;同时 settings.json 那条是真正的回归钉(base 本来就已门控),而非照着现状写出来的测试。

4. 无 mock 真实运行 E2E ✅

用编译产物中的真实 Config + ReadFileTool 驱动——不走 vitest、不使用 mock 的 config 对象(PR 自带单测把 getPlansDir 打了桩,本项则走真实 Config/Storage 路径)。plan 文件由产品自身的 config.savePlan() 写入,即用户批准 plan 时真正执行的代码,运行在临时 QWEN_HOME 下:

read_file 目标 修复前(origin/main 修复后(PR 7678)
已保存 plan 文件 ~/.qwen/plans/<session>.md ask ✗(会卡住) allow ← 本次修复
~/.qwen/settings.json ask ask ✓(不变)
~/.qwen/plans-secret/x.md(前缀兄弟目录) ask ask ✓(边界安全)
工作区内文件 allow allow
工作区外无关文件 ask ask

两次完整运行的 diff 只有两行——恰好只有一个权限单元格发生变化。前缀兄弟目录仍为 ask,因为 isSubpath() 用的是 path.relative 边界判断而非字符串前缀匹配。

5. 本轮新增 —— 配置 plansDirectory 能逃出项目根吗?🔍

PR 的「风险与范围」称:由于配置的 plansDirectory 必须解析在项目根内,实际扩大的只有默认 ~/.qwen/plans。第 1 轮我是通过读代码认可这一点的;本轮直接对真实 Config 做了探测:

配置的 plansDirectory 结果
"plans"(根内) 允许 —— 且本就在 PR 之前即工作区可读,不构成新增读面
"../escape" 拒绝 —— FatalConfigError
"/etc" 拒绝 —— FatalConfigError
"~/.ssh" 拒绝 —— FatalConfigError

没有任何配置值能逃出项目根,因此新增的免确认读面确实只有默认的 <QWEN_HOME>/plans。风险与范围一节的表述准确。

6. 4 个改动文件的门禁 ✅

prettier --check 通过 · eslint --max-warnings 0 exit 0 · tsc --noEmitpackages/corepackages/cli 两侧均通过(第 1 轮仅覆盖 core)。上游 CI 亦为绿。

证据

见上方英文部分的截图。

结论: 已在今天的 main 上复验。合并干净,第 1 轮全部结论复现,权限扩大严格限定为一类路径,且原先靠推断的范围限制现已获实证。无阻塞项,从验证角度可以合并。👍

@wenshao
wenshao added this pull request to the merge queue Jul 25, 2026
Merged via the queue into QwenLM:main with commit 331d58c Jul 25, 2026
78 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants