Skip to content

fix(core): keep the ask_user_question dialog behind allow rules and auto-approval - #10160

Open
qqqys wants to merge 3 commits into
QwenLM:mainfrom
qqqys:fix/ask-user-question-requires-interaction
Open

fix(core): keep the ask_user_question dialog behind allow rules and auto-approval#10160
qqqys wants to merge 3 commits into
QwenLM:mainfrom
qqqys:fix/ask-user-question-requires-interaction

Conversation

@qqqys

@qqqys qqqys commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

ask_user_question now declares requiresUserInteraction() whenever a host can actually show its dialog (interactive TUI, or an ACP / stream-json host), so the question dialog can no longer be skipped by a permission rule or an automatic approval mode. The permission flow already forces 'ask' for interaction-required invocations regardless of L4 allow rules, and the scheduler already refuses to auto-approve them alongside a sibling — this change simply opts the tool into that path. Headless runs are unchanged: nothing can prompt there, the flag stays false, and execute() keeps returning its existing "cannot ask user questions in non-interactive mode" message. The three mode checks in the tool are folded into one canCollectAnswers() helper so the permission default, the interaction flag, and execute() cannot drift apart again.

Two layers of regression tests are added: tool-level (requiresUserInteraction() is true interactively and for ACP hosts, false headless) and flow-level with the real AskUserQuestionTool, the real PermissionManager, and the exact applySkillAllowedTools grant a skill's allowedTools applies — asserting that the grant does override the default to allow at L4 yet evaluatePermissionFlow still yields 'ask' and needsConfirmation is true even in YOLO, that headless still yields 'allow', and that an explicit deny rule is preserved.

Why it's needed

The confirmation dialog is this tool: its answers are collected through onConfirm. Approving it without the dialog does not "allow" the tool, it silently answers "declined" on the user's behalf, and the tool then reports User declined to answer the questions. as a successful result. Any bare ask_user_question allow rule triggers exactly that — a skill's allowedTools grant (applied as a session-wide allow rule via applySkillAllowedToolsaddSessionAllowRule), a permissions.allow entry, or an "always allow" answer. The review of #10002 caught this live: with the grant loaded the scheduler went validating → scheduled → executing → success with no awaiting_approval, and the skill drafted on a fabricated refusal; the fix there was to drop the grant from that one skill, and the reviewer flagged the root cause as a maintainer call. Two bundled skills on main still carry the same grant (batch, extension-creator), and once any skill with it has loaded, every later ask_user_question in that session loses its dialog too — including the ACP and Web Shell paths. The fabricated "declined" also lands in the transcript as evidence, which the Goal verifier is designed to trust.

Reviewer Test Plan

How to verify

  1. cd packages/core && npx vitest run src/tools/askUserQuestion.test.ts src/core/permissionFlow.test.ts src/tools/exitPlanMode.test.ts src/core/coreToolScheduler.test.ts — all pass; the three new permissionFlow cases and three new askUserQuestion cases are the regression.
  2. Interactive, in Ask approval mode: run /batch or /extension-creator (both ship allowedTools: [ask_user_question]) and drive them to a clarifying question. Before: the question never appears and the model continues with "User declined to answer the questions." After: the question dialog renders; answering feeds the answers back.
  3. Interactive, YOLO (-y): same — the question dialog still appears (YOLO already special-cased this tool by name; the flag now covers rule-based allows the same way).
  4. Headless: qwen -p "Use the ask_user_question tool to ask me which framework I prefer" behaves exactly as before — in a plain -p run the tool is not even registered (the model reports No tools found matching 'keyword:ask_user_question'), and the false branch of the flag for non-interactive, non-ACP configs is pinned by the new unit tests; no new "requires user approval but cannot execute in non-interactive mode" warning.

Evidence (Before & After)

Before (from the #10002 review probe, real PermissionManager + CoreToolScheduler, grant loaded from a SKILL.md): WITH grant: L3 'ask' -> L4 'allow'; scheduler [validating, scheduled, executing, success] (no awaiting_approval, dialog never shown) -> tool result: "User declined to answer the questions." (executionStatus: success).

After — the same grant, through evaluatePermissionFlow (new test keeps the dialog when a skill's allowedTools grant would otherwise allow the tool): defaultPermission: 'ask', pm.evaluate(ctx): 'allow', requiresUserInteraction: true, finalPermission: 'ask', needsConfirmation(…, YOLO, …): true. Headless counterpart: requiresUserInteraction: false, finalPermission: 'allow'.

Tested on

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

Environment (optional)

Linux, Node 22, npm ci + npm run build + npm run bundle; core tsc --noEmit clean; 460 core tests (askUserQuestion, permissionFlow, exitPlanMode, coreToolScheduler) and 832 cli tests (permissionController, permissionUtils, Session, gemini) pass; headless -p run for step 4 against DashScope qwen3.8-max.

Risk & Scope

  • Main risk or tradeoff: for interaction-required invocations the scheduler sets hideAlwaysAllow and skips them in autoApproveCompatiblePendingTools; for this tool both are the desired behavior (there is no meaningful "always allow" for a question, and approving a sibling must not answer a question). In AUTO mode the classifier fast path is skipped for this tool — it already fell back to the dialog for it.
  • Not validated / out of scope: the ask_user_question entries in batch and extension-creator SKILL.md are left in place — with this change they are inert rather than harmful, and removing them is a separate cleanup. No change to ACP hosts' handling of the confirmation request; they already received 'ask' for this tool.
  • Breaking changes / migration notes: a permissions.allow rule for ask_user_question no longer suppresses the dialog. That rule never produced answers, only silent refusals, so nothing that worked before stops working.

Linked Issues

Follow-up to #10002 (review finding on the ask_user_question grant).

中文说明

这个 PR 做了什么

ask_user_question 现在在宿主能真正弹出对话框的场景(交互式 TUI,或 ACP / stream-json 宿主)下声明 requiresUserInteraction(),因此它的提问对话框不再会被权限规则或自动审批模式跳过。权限流程本来就会对"需要用户交互"的调用强制返回 'ask'(无视 L4 的 allow 规则),调度器也本来就不会把它随同级工具一起自动批准——本改动只是让这个工具走上这条路径。headless 运行不变:那里没有任何东西能弹窗,标志保持 falseexecute() 仍返回原有的"非交互模式下无法向用户提问"消息。工具里三处模式判断合并成一个 canCollectAnswers() 辅助函数,避免权限默认值、交互标志和 execute() 再次各自漂移。

新增两层回归测试:工具级(交互和 ACP 宿主下 requiresUserInteraction()true,headless 下为 false),以及 permissionFlow 级——用真实的 AskUserQuestionTool、真实的 PermissionManager 和 skill 的 allowedTools 实际施加的 applySkillAllowedTools 授权,断言该授权确实在 L4 把默认值覆盖成了 allow,但 evaluatePermissionFlow 仍然给出 'ask'、即使在 YOLO 下 needsConfirmation 也为 true;headless 仍为 'allow';显式 deny 规则保留。

为什么需要

确认对话框就是这个工具本身:答案是通过 onConfirm 收集的。不弹对话框就"批准"它,并不是允许了这个工具,而是替用户默默回答了"拒绝",工具随后把 User declined to answer the questions. 当作成功结果返回。任何裸的 ask_user_question allow 规则都会触发这一点——skill 的 allowedTools 授权(通过 applySkillAllowedToolsaddSessionAllowRule 以会话级 allow 规则施加)、permissions.allow 条目、或一次"总是允许"。#10002 的评审现场抓到了这个问题:加载授权后调度器直接 validating → scheduled → executing → success,没有 awaiting_approval,skill 基于伪造的拒绝继续起草;当时的修法是从那一个 skill 里去掉授权,评审把根因标为维护者决策。main 上还有两个内置 skill 带着同样的授权(batchextension-creator),而且一旦任何带此授权的 skill 加载过,该会话里之后所有 ask_user_question 的对话框都会失效——包括 ACP 和 Web Shell 路径。伪造的"拒绝"还会作为证据留在 transcript 里,而 Goal verifier 正是被设计成信任 transcript 的。

Reviewer 测试计划

如何验证

  1. cd packages/core && npx vitest run src/tools/askUserQuestion.test.ts src/core/permissionFlow.test.ts src/tools/exitPlanMode.test.ts src/core/coreToolScheduler.test.ts——全部通过;permissionFlow 新增的三个用例和 askUserQuestion 新增的三个用例就是回归测试。
  2. 交互模式、Ask 审批模式:运行 /batch/extension-creator(两者都带 allowedTools: [ask_user_question]),推进到一个澄清提问。Before:提问永远不出现,模型以"User declined to answer the questions."继续。After:提问对话框渲染出来;回答后答案回传。
  3. 交互模式、YOLO-y):同上——提问对话框仍然出现(YOLO 本来就按工具名特判了它;现在标志以同样方式覆盖了基于规则的 allow)。
  4. Headless:qwen -p "Use the ask_user_question tool to ask me which framework I prefer" 行为与之前完全一致——普通 -p 运行里该工具根本没有注册(模型报告 No tools found matching 'keyword:ask_user_question'),非交互、非 ACP 配置下标志为 false 的分支由新增单元测试锁定;没有新增"requires user approval but cannot execute in non-interactive mode"警告。

证据(Before & After)

Before(来自 #10002 评审探针,真实 PermissionManager + CoreToolScheduler,授权从 SKILL.md 加载):WITH grant: L3 'ask' -> L4 'allow'; scheduler [validating, scheduled, executing, success](无 awaiting_approval,对话框从未显示)-> 工具结果:"User declined to answer the questions."(executionStatus: success)

After——同样的授权经过 evaluatePermissionFlow(新测试 keeps the dialog when a skill's allowedTools grant would otherwise allow the tool):defaultPermission: 'ask'pm.evaluate(ctx): 'allow'requiresUserInteraction: truefinalPermission: 'ask'needsConfirmation(…, YOLO, …): true。headless 对照:requiresUserInteraction: falsefinalPermission: 'allow'

测试平台

Linux ✅;macOS、Windows ⚠️ 未测试。

环境(可选)

Linux,Node 22,npm ci + npm run build + npm run bundle;core tsc --noEmit 干净;core 460 个测试(askUserQuestion、permissionFlow、exitPlanMode、coreToolScheduler)与 cli 832 个测试(permissionController、permissionUtils、Session、gemini)通过;第 4 步为一次针对 DashScope qwen3.8-max 的 headless -p 运行。

风险与范围

  • 主要风险/取舍:对需要交互的调用,调度器会设置 hideAlwaysAllow 并在 autoApproveCompatiblePendingTools 中跳过它们;对这个工具而言两者都是期望行为(对提问不存在有意义的"总是允许",批准同级工具也不应替用户回答问题)。AUTO 模式下会跳过分类器快速路径——它对该工具本来就回落到对话框。
  • 未验证/范围外:batchextension-creatorSKILL.md 里的 ask_user_question 条目保留不动——有了本改动它们从有害变成无效,移除属于另一次清理。不改变 ACP 宿主对确认请求的处理;它们对该工具本来就收到 'ask'
  • 破坏性变更/迁移说明:permissions.allow 里针对 ask_user_question 的规则不再能抑制对话框。这条规则从来没产生过答案、只产生过静默拒绝,所以之前能工作的东西没有一样会停止工作。

关联 Issue

#10002 的后续(评审对 ask_user_question 授权的发现)。

https://claude.ai/code/session_01FV7i3w7egJ2kMw4AhQC38Z

…uto-approval

The confirmation dialog is this tool: answers are collected through
onConfirm. Approving it without the dialog does not allow the tool, it
silently answers "declined" on the user's behalf, and execute() then
reports "User declined to answer the questions." as a successful result.
Any bare ask_user_question allow rule does exactly that — a skill's
allowedTools grant (applied session-wide via applySkillAllowedTools), a
permissions.allow entry, or an "always allow" answer — and once one has
loaded, every later question in the session loses its dialog too.

The invocation now declares requiresUserInteraction() whenever a host can
show the dialog (interactive TUI, ACP / stream-json hosts), so the
permission flow forces 'ask' regardless of L4 allow rules and the
scheduler never auto-approves it beside a sibling. Headless runs are
unchanged: nothing can prompt there, the flag stays false, and execute()
keeps returning its existing non-interactive message. The three mode
checks are folded into one canCollectAnswers() helper so the permission
default, the flag, and execute() cannot drift apart.

Tests pin the flag per mode and, at the permissionFlow level with the real
tool, the real PermissionManager and the real allowedTools grant, that the
grant overrides the default to allow at L4 yet the flow still yields 'ask'
(and needsConfirmation is true even in YOLO), that headless still yields
'allow', and that an explicit deny rule is preserved.

Follow-up to the QwenLM#10002 review finding on the ask_user_question grant.

Claude-Session: https://claude.ai/code/session_01FV7i3w7egJ2kMw4AhQC38Z
@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: observed, not theoretical — and I verified the mechanism independently in the base code: a bare ask_user_question allow rule overrides the 'ask' default at L4, the scheduler then executes without ever showing the dialog, wasAnswered stays false, and execute() reports User declined to answer the questions. as a success. The exposure is real today: both batch and extension-creator bundled skills on main declare allowedTools: [ask_user_question], which lands as a session-wide allow rule via applySkillAllowedTools. The #10002 review observation is consistent with all of this.

Direction: aligned. This is a correctness fix to the approval flow — the dialog being skippable means the tool silently answers on the user's behalf. Claude Code's changelog shows fixes in the same problem class ("Fixed auto mode suppressing AskUserQuestion when the user or a skill explicitly relies on it"), so the area is clearly relevant.

Size: core paths touched (packages/core/src/tools/**) — 42 production lines (askUserQuestion.ts +30/−12), 146 test lines, 0 generated. Well below any size threshold; Tier 2 full-confidence review applies.

Approach: minimal and idiomatic — it opts the tool into the existing requiresUserInteraction mechanism (the one exit_plan_mode uses since #7671) instead of adding a new special case, and folds the three mode checks that could drift into one canCollectAnswers(). Leaving the bundled-skill grants in place (now inert) for a separate cleanup is the right scope call.

Risk: no high-risk-path matches from the revert-history signal. One downstream consumer needs close attention in code review: the stream-json permission controller also branches on requiresUserInteraction — checking that in Stage 2.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题: 已观测到的 bug,不是理论问题——我在 base 代码里独立验证了机制:裸的 ask_user_question allow 规则会在 L4 覆盖 'ask' 默认值,调度器随后不弹对话框直接执行,wasAnswered 保持 falseexecute()User declined to answer the questions. 当作成功结果返回。当前就有真实暴露面:main 上的 batchextension-creator 两个内置 skill 都声明了 allowedTools: [ask_user_question],会通过 applySkillAllowedTools 落为会话级 allow 规则。#10002 评审的现场观察与此完全一致。

方向: 对齐。这是审批流的正确性修复——对话框可被跳过意味着工具在替用户默默作答。Claude Code 的 changelog 里有同一问题域的修复("Fixed auto mode suppressing AskUserQuestion when the user or a skill explicitly relies on it"),该方向显然相关。

规模: 触及核心路径(packages/core/src/tools/**)——42 行生产代码(askUserQuestion.ts +30/−12),146 行测试,0 行生成代码。远低于任何规模阈值;适用 Tier 2 全置信度审查。

方案: 最小且符合惯例——复用 exit_plan_mode#7671)以来现成的 requiresUserInteraction 机制而非新增特判,并把三处可能漂移的模式判断合并为一个 canCollectAnswers()。把内置 skill 的授权条目原地保留(现已无害)、留到单独清理,范围取舍正确。

风险: 回滚历史信号无高风险路径命中。代码审查时需重点看一个下游消费方:stream-json 权限控制器也会按 requiresUserInteraction 分支——在 Stage 2 中核实。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at 90d604d4c2ca23235a7e80f218c5bd7f4de4c913 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Code review

The core change is sound and I verified each claim against the base code: evaluatePermissionFlow forces 'ask' for interaction-required invocations unless denied, needsConfirmation returns true ahead of the YOLO bypass, the scheduler skips them in autoApproveCompatiblePendingTools / the AUTO fast path / AUTO_EDIT and sets hideAlwaysAllow, and canCollectAnswers() is semantics-identical to the three checks it replaces. The exit_plan_mode precedent (#7671) is the right mechanism to reuse.

But the flag has one more downstream consumer the PR doesn't account for, and it breaks there:

Critical — stream-json hosts lose the user's answers. In packages/cli/src/nonInteractive/control/controllers/permissionController.ts, handleOutgoingPermissionRequest is wired into the stream-json execution path (nonInteractiveCli.ts passes getToolCallUpdateCallback() to the scheduler) and serves every awaiting_approval call. For behavior === 'allow' it has a requiresUserInteraction early return — added for exit_plan_mode, where no payload matters — that confirms with ProceedOnce and no payload, before the buildAllowConfirmationPayload step. That payload step is exactly the documented answer channel for this tool: the SDK host returns updatedInput.answers, the controller promotes them into payload.answers, and the tool reads its answers from there (pinned by the existing test "routes ask_user_question answers from updatedInput into the confirmation payload"). With this PR, requiresUserInteraction() is true for every stream-json invocation, so every answered question now takes the payload-less branch: userAnswers = payload?.answers ?? {} → the tool reports "No valid answers were provided." even though the user answered in the host's dialog. Before this PR the flag was false in stream-json mode and answers routed correctly — so this is a regression the PR introduces on the very host path the description says is unaffected ("No change to ACP hosts' handling"). The Zed ACP path (acp-integration/session/Session.ts) is fine — it builds { answers: output.answers } after requestPermission with no such early return — but the stream-json control path is not.

Two reasons nothing in CI catches it: the existing controller test's mock tool call carries no invocation, so requiresUserInteraction stays undefined and the test passes either way; and the new flow-level tests stop at evaluatePermissionFlow, one layer above the controller. Suggested shape of the fix: have the requiresUserInteraction allow-branch route the confirmation payload too (or at least the promoted answers) instead of returning bare, plus a regression test that drives a real AskUserQuestionTool invocation through the controller with an updatedInput.answers response. Also worth a look while there: that early return drops the host's updatedInput args-sanitization as well — intentional for plan-exit, but now that a second tool opts into the flag, the branch deserves an explicit comment about what it may and may not drop.

Non-blocking: with the flag in place, the name-based ask_user_question special case in needsConfirmation's YOLO check is now redundant for this tool (the flag short-circuits first). Fine to keep as defense in depth; not asking for a change.

Test evidence

Unattended CI run — I do not execute PR code; the evidence below is the PR's own CI fetched via the API at the reviewed commit, plus static review. The main unit suite is still running at review time; interactive/TUI behavior is not verified here (author's Linux test report is their claim, not evidence). The finalize workflow updates the table once CI settles.

Final CI results for 90d604d (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Classify PR ✅ success
Dependency CVE audit ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Secret scan (TruffleHog) ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

Sandboxed verification would settle what unit tests cannot: @qwen-code /verify for the claim that a loaded skill grant no longer suppresses the dialog end-to-end, and @qwen-code /tmux for the TUI surface itself — that under Ask mode (and YOLO) the question dialog actually renders and answers flow back. The author tested interactively on Linux only; neither claim is independently substantiated yet. (Moot until the stream-json regression above is fixed — a /verify run would re-prove the bug on the SDK path as it stands.)

中文说明

代码审查

核心改动是成立的,我对照 base 代码逐条验证了 PR 的声明:evaluatePermissionFlow 会对需要交互的调用强制 'ask'(deny 除外),needsConfirmation 在 YOLO 旁路之前返回 true,调度器在 autoApproveCompatiblePendingTools、AUTO 快速路径、AUTO_EDIT 中都跳过它们并设置 hideAlwaysAllow,且 canCollectAnswers() 与被替换的三处判断语义完全一致。复用 exit_plan_mode#7671)的机制是正确选择。

但这个标志还有一个 PR 没有考虑到的下游消费方,并且在那里出了问题:

Critical — stream-json 宿主会丢失用户的回答。 packages/cli/src/nonInteractive/control/controllers/permissionController.tshandleOutgoingPermissionRequest 被接在 stream-json 执行路径上(nonInteractiveCli.tsgetToolCallUpdateCallback() 传给调度器),服务所有 awaiting_approval 调用。behavior === 'allow' 分支里有一个 requiresUserInteraction 的提前返回——它是为 exit_plan_mode 加的(那里不需要 payload)——直接用不带 payload 的 ProceedOnce 确认,发生在 buildAllowConfirmationPayload 之前。而那一步正是本工具有文档记载的回答通道:SDK 宿主通过 updatedInput.answers 返回答案,控制器将其提升为 payload.answers,工具从那里读取答案(现有测试 "routes ask_user_question answers from updatedInput into the confirmation payload" 锁定了此行为)。本 PR 之后,stream-json 下每次调用的 requiresUserInteraction() 都为 true,于是每个已回答的问题都走不带 payload 的分支:userAnswers = payload?.answers ?? {} → 即使用户已经在宿主对话框里作答,工具仍报告 "No valid answers were provided."。本 PR 之前 stream-json 模式下该标志为 false,回答路由正常——所以这是 PR 在描述中声称"不受影响"("不改变 ACP 宿主的处理")的宿主路径上引入的回归。Zed ACP 路径(acp-integration/session/Session.ts)没问题——它在 requestPermission 之后构造 { answers: output.answers },没有这样的提前返回——但 stream-json 控制路径有问题。

CI 抓不到它的两个原因:现有控制器测试的 mock 调用不带 invocationrequiresUserInteraction 恒为 undefined,测试两种情况下都通过;新增的 flow 级测试止步于 evaluatePermissionFlow,在控制器的上一层。建议的修法:让 requiresUserInteraction 的 allow 分支也传递确认 payload(至少传递提升后的 answers)而不是裸返回,并补一个用真实 AskUserQuestionTool 调用、以带 updatedInput.answers 的响应驱动控制器的回归测试。顺带值得看一眼:该提前返回还会丢弃宿主的 updatedInput 参数净化——对 plan-exit 是有意的,但现在第二个工具也用上了这个标志,这个分支值得加一条明确注释说明它可以丢什么、不可以丢什么。

非阻塞:有了该标志后,needsConfirmation YOLO 判断里按名字的 ask_user_question 特判对本工具变成冗余(标志先短路)。可以留作双保险,不要求改动。

测试证据

无人值守 CI 运行——不执行 PR 代码;以下证据来自评审提交点上通过 API 获取的 PR 自身 CI,以及静态审查。评审时主单测套件仍在运行;交互/TUI 行为未在此验证(作者的 Linux 测试报告是其自述,不是证据)。finalize 工作流会在 CI 落定后更新表格。

(表格见上方机器可读区块)

沙箱验证能补上单测补不了的部分:@qwen-code /verify 验证"加载 skill 授权后对话框不再被抑制"这一端到端声明,@qwen-code /tmux 验证 TUI 表面本身——Ask 模式(及 YOLO)下提问对话框确实渲染、答案确实回传。作者只在 Linux 上做了交互测试;两个声明目前都没有独立证实。(在上面的 stream-json 回归修复之前这些都是空谈——现在跑 /verify 只会在 SDK 路径上再次复现该 bug。)

Qwen Code · qwen3.8-max

Reviewed at 90d604d4c2ca23235a7e80f218c5bd7f4de4c913 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 2/5 — right fix, right mechanism, genuinely good tests, but it regresses the stream-json answer channel and cannot ship as-is.

Stepping back: my independent proposal for this problem was exactly what this PR does — opt the tool into requiresUserInteraction, the mechanism built for it, and collapse the three drifting mode checks into one. So the approach earns no skepticism; if anything it's cleaner than what I'd have drafted. The motivation is real (verified from base code, not just taken on faith), the problem bites today via two bundled skills, and the test design — real PermissionManager plus the exact applySkillAllowedTools grant — is the right way to pin it.

What sinks this round is the thing the gate exists to catch: naming every downstream consumer of a core-path flag. requiresUserInteraction doesn't only mean "force 'ask'" — in the stream-json permission controller it also selects a payload-less confirmation branch, and that branch silently discards the answers an SDK host just collected. It's the same failure mode this PR sets out to kill, one layer down: instead of a fabricated "declined", a fabricated "no answers", on the path the description explicitly claims is untouched. The fix is small and the rest of the PR is ready, so this should turn around quickly — route the payload in that branch, add the controller-level regression test, and this is in good shape to re-review. (CI note: the unit suite was still running at review time; it wouldn't have caught this anyway, per the Stage 2 comment.)

Requesting changes on the stream-json regression. 🙏

中文说明

置信度:2/5 —— 修复方向正确、机制正确、测试确实写得好,但它让 stream-json 回答通道回归,现状不能合入。

退一步看:我对这个问题的独立方案与本 PR 完全一致——让工具接入为它而建的 requiresUserInteraction 机制,并把三处会漂移的模式判断合并为一处。所以方案本身无可置疑;它甚至比我预想的更干净。动机真实(从 base 代码验证过,不是照单全收),问题今天就通过两个内置 skill 实际发生,测试设计——真实 PermissionManager 加上 applySkillAllowedTools 的原样授权——正是锁定它的正确方式。

让这一轮失败的是门槛检查本该抓住的事:点名核心路径标志的每一个下游消费方。requiresUserInteraction 不只意味着"强制 'ask'"——在 stream-json 权限控制器里它还选中了一个不带 payload 的确认分支,而那个分支会悄悄丢掉 SDK 宿主刚收集到的答案。这正是本 PR 要消灭的失效模式在下一层的翻版:把伪造的"拒绝"换成了伪造的"没有回答",而且发生在描述里明确声称未触碰的路径上。修复很小,PR 其余部分已就绪,所以应该很快能回来——在该分支里传递 payload、补上控制器级回归测试,再评审时状态就会很好。(CI 说明:评审时单测套件仍在运行;无论如何它也抓不到这个问题,见 Stage 2 评论。)

就 stream-json 回归请求修改。🙏

Qwen Code · qwen3.8-max

Reviewed at 90d604d4c2ca23235a7e80f218c5bd7f4de4c913 · 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.

Needs some rethinking — the stream-json control path drops the user's answers once the flag flips on (see the Critical finding in my review comment above). The fix is small: route the confirmation payload through the requiresUserInteraction allow-branch in permissionController.handleOutgoingPermissionRequest, and add a controller-level regression test. Everything else looks ready. 🙏

@qqqys

qqqys commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

已修复 stream-json 回答丢失:62aac5b85d 将无 payload 的提前确认限定为 exit_plan_modeask_user_question 现在会继续转发 host 的 updatedInputanswers

验证:controller 29/29、相关 core 47/47、全仓 build/typecheck、目标 ESLint/Prettier 均通过;真实 AskUserQuestionTool probe 已确认答案进入 payload、参数更新且最终输出包含实际回答。

@qqqys

qqqys commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Aug 26, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. This is a fork PR, so the first round comes from the next scheduled scan (usually within minutes). Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。本 PR 来自 fork,首轮处理将由下一次定时扫描执行(通常几分钟内)。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@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.

Partially reviewed — gaps disclosed. Suggestions are inline.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not explored to full depth (tool budget reached): "agent 6a": did not execute the new/changed unit tests (permissionFlow.test.ts, askUserQuestion.test.ts, permissionController.test.ts) — the worktree has no node_modules /….

中文说明

仅完成部分审查,审查缺口已披露。 建议见行内评论。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):"agent 6a"did not execute the new/changed unit tests (permissionFlow.test.ts, askUserQuestion.test.ts, permissionController.test.ts) — the worktree has no node_modules /…

— qwen3.8-max via Qwen Code /review (v0.22.2)

Comment on lines +180 to +185
private canCollectAnswers(): boolean {
const isAcpMode =
this._config.getExperimentalZedIntegration() ||
this._config.getInputFormat() === InputFormat.STREAM_JSON;
return this._config.isInteractive() || isAcpMode;
}

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.

[Suggestion] canCollectAnswers() is a fifth copy of the host-capability predicate. resolveInteractionMode(config) !== 'headless' in packages/core/src/core/prompts.ts already expresses exactly this ("is there a host that can put the questions in front of the user"), and config.ts already uses it (supportsUserInteraction) to gate the registration of ask_user_question itself — so the registration gate and this runtime gate are two separately written copies of the same predicate. If a new answerable host or input format is added, config.ts and the scheduler copies (enterPlanMode.ts, coreToolScheduler.ts) can be updated while canCollectAnswers() is missed — or vice versa: the tool then registers and the system prompt encourages asking, but getDefaultPermission() returns 'allow', requiresUserInteraction() returns false, and execute() refuses with "Cannot ask user questions in non-interactive mode without ACP support" — the exact drift resolveInteractionMode's own comment warns about. Delegating keeps one source of truth (needs the resolveInteractionMode import from ../core/prompts.js):

Suggested change
private canCollectAnswers(): boolean {
const isAcpMode =
this._config.getExperimentalZedIntegration() ||
this._config.getInputFormat() === InputFormat.STREAM_JSON;
return this._config.isInteractive() || isAcpMode;
}
private canCollectAnswers(): boolean {
return resolveInteractionMode(this._config) !== 'headless';
}

If you apply this, run the requiresUserInteraction suite in src/tools/askUserQuestion.test.ts as the mutation check — it pins the mode truth table (interactive / stream-json / Zed → true, headless → false) and goes red if the delegation breaks that equivalence.

中文说明

canCollectAnswers() 是"宿主能否弹出对话框"这一谓词的第五份拷贝。packages/core/src/core/prompts.ts 里的 resolveInteractionMode(config) !== 'headless' 已经精确表达了同一语义,而且 config.ts 已经用它(supportsUserInteraction)来控制 ask_user_question 本身的注册——注册开关与这里的运行时开关现在是同一谓词的两份独立实现。将来新增一种可作答的宿主或输入格式时,config.ts 和调度器里的拷贝(enterPlanMode.tscoreToolScheduler.ts)可能更新了而 canCollectAnswers() 被漏掉(或反过来):工具照常注册、系统提示词鼓励提问,但 getDefaultPermission() 返回 'allow'requiresUserInteraction() 返回 falseexecute() 报"Cannot ask user questions in non-interactive mode without ACP support"——正是 resolveInteractionMode 自身注释所警告的漂移。委托给它即可保持单一事实来源(需要从 ../core/prompts.js 导入 resolveInteractionMode)。

若应用该修改,请把 src/tools/askUserQuestion.test.tsrequiresUserInteraction 套件作为变异检查重新运行——它锁定了模式真值表(交互 / stream-json / Zed → true,headless → false),一旦委托破坏等价性就会变红。

— qwen3.8-max via Qwen Code /review (v0.22.2)

Comment on lines +577 to +580
if (
requiresUserInteraction &&
toolCall.request.name === ToolNames.EXIT_PLAN_MODE
) {

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.

[Suggestion] This name check is the whole fix of the stream-json answer-drop regression — the unscoped if (requiresUserInteraction) at the base commit confirmed with no payload, silently discarding the answers the SDK host had just collected — yet nothing in the code says why exit_plan_mode alone takes the payload-less branch. The rationale currently lives only in two tests, so a future maintainer "simplifying" the seemingly redundant toolCall.request.name === ToolNames.EXIT_PLAN_MODE condition re-broadens the guard and drops stream-json answers again — the exact regression this PR fixes. Conversely, someone adding a third outcome-only interactive tool has no in-code signal about which path it must take, and host updatedInput can silently overwrite the args the user approved. A short comment pins the contract:

Suggested change
if (
requiresUserInteraction &&
toolCall.request.name === ToolNames.EXIT_PLAN_MODE
) {
// exit_plan_mode approves through the dialog alone: its onConfirm takes
// no payload and the approved plan must not be replaced by the host's
// updatedInput. Any other requiresUserInteraction tool (e.g.
// ask_user_question) must take the updatedInput path below — that
// channel carries the user's answers.
if (
requiresUserInteraction &&
toolCall.request.name === ToolNames.EXIT_PLAN_MODE
) {
中文说明

这个工具名判断正是 stream-json 回答丢失回归的全部修复——base 提交上未加限定的 if (requiresUserInteraction) 会不带 payload 直接确认,悄悄丢掉 SDK 宿主刚收集到的答案——但代码里没有任何地方说明为什么只有 exit_plan_mode 走这条不带 payload 的分支。理由目前只存在于两个测试里:未来某位维护者"简化"这个看似冗余的 toolCall.request.name === ToolNames.EXIT_PLAN_MODE 条件,就会重新放宽守卫、再次丢掉 stream-json 的答案——正是本 PR 修复的回归。反过来,若有人新增第三个"只要结果"的交互工具,也没有任何代码内信号告诉它该走哪条路径,宿主的 updatedInput 可能悄悄覆盖用户批准的参数。一条简短注释即可锁定该约定。

— qwen3.8-max via Qwen Code /review (v0.22.2)

@qwen-code-dev-bot

qwen-code-dev-bot commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

AutoFix round 2 finishedview run. See this round's report below.

中文说明

AutoFix 第 2 轮已完成 —— 查看运行。本轮报告见下方。

…Mode (QwenLM#10160)

canCollectAnswers() was a fifth copy of the host-capability predicate;
delegating to resolveInteractionMode keeps one source of truth shared
with the tool-registration gate. Also pin the stream-json payload
contract in permissionController with a comment: only exit_plan_mode
may approve through the payload-less branch.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下:

Round summary

Addressed both new inline suggestions from the re-review at sha 62aac5b85d, and verified the earlier CHANGES_REQUESTED stream-json fix is already in HEAD. One additive commit: 1190f0b0df (net −2 source lines + one requested comment).

Feedback points and dispositions

  • [rv:5030784374] CHANGES_REQUESTED — stream-json control path drops the user's answers: already fixed in HEAD before this round. Commit 62aac5b85d scoped the payload-less early confirmation to exit_plan_mode alone, so ask_user_question now takes the updatedInput path whose confirmation payload carries answers. Verified against the current code and by running the controller suite: the regression tests "routes ask_user_question answers from updatedInput into the confirmation payload" and "treats stream-json can_use_tool allow as explicit interaction without replacing the plan" both pass (29/29 in the file).
  • [rc:3864042219] Suggestion — canCollectAnswers() is a fifth copy of the host-capability predicate: implemented. canCollectAnswers() now delegates to resolveInteractionMode(config) !== 'headless', the single source of truth that already gates registration of the tool itself (supportsUserInteraction in config.ts). Equivalence with the old inline predicate was verified against the full truth table (interactive / stream-json / Zed / headless) before editing; the now-unused InputFormat import was removed. Mutation probe: negating the delegation fails 13 tests across askUserQuestion.test.ts and permissionFlow.test.ts; restored, they are green.
  • [rc:3864042240] Suggestion — the exit_plan_mode name check is load-bearing but undocumented: implemented. Added a comment above the guard in handleOutgoingPermissionRequest pinning the contract: exit_plan_mode approves through the dialog alone (its onConfirm takes no payload, and the approved plan must not be replaced by the host's updatedInput), while every other requiresUserInteraction tool must take the updatedInput path that carries the user's answers.
  • [rv:5031979794] COMMENTED — partially reviewed, gaps disclosed: the disclosed gap (the new/changed unit tests were never executed in the reviewer's worktree) is covered by this round's verification runs below.

No base conflict (--conflict false). Nothing declined, deferred, or escalated this round.

Verification

  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed (no ESLint errors or warnings)
  • npx prettier --check on the two changed files — passed
  • vitest packages/core (askUserQuestion.test.ts + permissionFlow.test.ts + prompts.test.ts, touched) — 156 passed (156)
  • vitest packages/cli (permissionController.test.ts, touched) — 29 passed (29)
  • Mutation probe on the delegation (negated !== 'headless'=== 'headless'): 13 failed | 34 passed (red as expected); after restore — 47 passed (47)
  • Integration tests: not run — the changed behavior is exercised by the unit suites above, not only through the bundled CLI or integration harness.
中文说明

本轮摘要

已处理 sha 62aac5b85d 复审中的两条新行内建议,并确认此前 CHANGES_REQUESTED 指出的 stream-json 修复已存在于 HEAD。新增一个提交:1190f0b0df(源码净 −2 行 + 一条按建议添加的注释)。

反馈点及处理结论

  • [rv:5030784374] CHANGES_REQUESTED —— stream-json 控制路径丢失用户回答:本轮之前已在 HEAD 修复。提交 62aac5b85d 已将"无 payload 的提前确认"限定为仅 exit_plan_mode,因此 ask_user_question 现在走 updatedInput 路径,其确认 payload 会携带 answers。已对照当前代码核实,并运行 controller 测试套件验证:回归测试 "routes ask_user_question answers from updatedInput into the confirmation payload" 与 "treats stream-json can_use_tool allow as explicit interaction without replacing the plan" 均通过(该文件 29/29)。
  • [rc:3864042219] 建议 —— canCollectAnswers() 是宿主能力谓词的第五份拷贝:已实现。canCollectAnswers() 现在委托给 resolveInteractionMode(config) !== 'headless' —— 这正是控制该工具注册与否(config.ts 中的 supportsUserInteraction)的单一事实来源。编辑前已对照完整真值表(交互 / stream-json / Zed / headless)核实新旧谓词等价;顺带移除了不再使用的 InputFormat 导入。变异探针:将委托取反后,askUserQuestion.test.tspermissionFlow.test.ts 共 13 个测试失败;还原后全部转绿。
  • [rc:3864042240] 建议 —— exit_plan_mode 工具名判断是修复的关键却没有说明:已实现。在 handleOutgoingPermissionRequest 的守卫上方补充注释,锁定该约定:exit_plan_mode 仅通过对话框本身完成批准(其 onConfirm 不接收 payload,且已批准的计划不得被宿主的 updatedInput 替换),而其他任何 requiresUserInteraction 工具都必须走承载用户回答的 updatedInput 路径。
  • [rv:5031979794] COMMENTED —— 部分审查、缺口已披露:其披露的缺口(审查者的 worktree 中从未执行过新增/变更的单元测试)已由本轮下方的验证运行覆盖。

无 base 冲突(--conflict false)。本轮没有拒绝、延后或升级处理的条目。

验证

  • npm run build —— 通过
  • npm run typecheck —— 通过
  • npm run lint —— 通过(无 ESLint 错误或警告)
  • 对两个变更文件运行 npx prettier --check —— 通过
  • vitest packages/core(askUserQuestion.test.ts + permissionFlow.test.ts + prompts.test.ts,本轮触及)—— 156 通过(156)
  • vitest packages/cli(permissionController.test.ts,本轮触及)—— 29 通过(29)
  • 针对委托的变异探针(将 !== 'headless' 取反为 === 'headless'):13 失败 | 34 通过(按预期变红);还原后 —— 47 通过(47)
  • 集成测试:未运行 —— 变更行为已由上述单元测试套件覆盖,并非只能通过打包后的 CLI 或集成测试框架验证。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@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.

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

— qwen3.8-max via Qwen Code /review (v0.22.2)

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Autofix round for PR #10160 — no code changes

Feedback triaged

  • [rv:5033403037] (automated reviewer, COMMENTED): "Partially reviewed — gaps disclosed. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally." The review reported no findings (empty findings list). This is a disclosed verification gap, not a defect claim. For context: that CI job only triggers on merge_group events (see the integration_cli job in ci.yml), so it is skipped by design on ordinary PR pushes.
  • No inline comments, issue-level comments, failed checks, or still-red checks were present. The diff-growth window is far under budget (source 2 / test 0 vs 400/400), and no growth audit was required.

Action taken: closed the disclosed gap by running the named suite locally

Model credentials equivalent to the merge-queue job's are available in this environment, so the exact suite the review named was run against a bundle rebuilt from the current HEAD:

  • npm run build + npm run bundle (fresh dist/cli.js at HEAD), then npm run test:integration:cli:sandbox:none.
  • Result: 212 tests — 193 passed, 18 skipped, 1 failed (38 files: 32 passed, 5 skipped, 1 failed).

The single failure is environment-specific and pre-existing (would fail identically on origin/main)

Failed test: cli/qwen-config-dir.test.ts > 1d: CLI functions normally when QWEN_HOME is not set, crashing at CLI bootstrap with EACCES: permission denied, mkdir '/home/github-runner/.qwen'.

Evidence chain:

  1. Filesystem probe: the job runs as uid=1000(node) while /home/github-runner is owned by root:root with mode 755. A plain touch /home/github-runner/.qwen-write-probe returns Permission denied; a bare node -e "fs.mkdirSync('/home/github-runner/.qwen')" returns EACCES. Nothing can create ~/.qwen in this environment, regardless of code.
  2. Direct CLI probe: running the bundled CLI with QWEN_HOME unset reproduces the exact stack from the test (writeOutputLanguageFileinitializeLlmOutputLanguagemain() in packages/cli/src/i18n/languageUtils.ts / gemini.tsx).
  3. Byte-identical to base: git diff origin/main HEAD for both crash-site files is empty; the test itself also exists unchanged on origin/main. The base branch crashes at the same spot for the same reason in this environment.
  4. Failure mode matches the cause exactly: every other test in the same file (and all 193 passing tests overall) sets QWEN_HOME to a writable temp directory; test 1d is the only test that relies on the default ~/.qwen under the non-writable home.
  5. Unrelated to this PR: the crash happens during CLI startup, before any tool/permission code runs; the PR's five changed files (ask_user_question permission flow, permissionController allow path, and their tests) are not on the failing path.

There is no in-scope code fix: hardening CLI bootstrap against a non-writable $HOME (or adjusting the test) lives outside this PR's footprint and is a maintainer's call — noted here as a possible follow-up candidate, not implemented. In the merge queue itself the job lands on runners with workspace/home ownership restoration, so this runner-local permission mismatch is not expected to reproduce there.

Why no code change this round

No finding was reported, no defect claim needed reproducing, and the one failing integration test is a pre-existing, environment-specific failure evidenced above. The PR branch is left as-is; nothing was committed.

Verification

  • npm run build — passed (all workspace packages)
  • npm run bundle — passed (dist/cli.js regenerated at HEAD)
  • npm run typecheck — passed
  • npm run lint — passed
  • vitest run src/core/permissionFlow.test.ts src/tools/askUserQuestion.test.ts (packages/core, touched) — 47 passed
  • vitest run src/nonInteractive/control/controllers/permissionController.test.ts (packages/cli, touched) — 29 passed
  • npm run test:integration:cli:sandbox:none — 193 passed / 18 skipped / 1 failed (qwen-config-dir.test.ts 1d; environment-specific and pre-existing, evidence above)
中文说明

PR #10160 的 Autofix 轮次 — 无代码变更

分类处理的反馈

  • [rv:5033403037](自动审查器,COMMENTED):"Partially reviewed — gaps disclosed. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally."(部分审查 — 已披露缺口。未审查:build-and-test — Integration Tests (CLI, No Sandbox) 在 CI 中被跳过,且该套件未在本地运行。)该审查未报告任何发现(发现列表为空)。这是一次已披露的验证缺口,而非缺陷指控。背景说明:该 CI 任务仅在 merge_group 事件上触发(见 ci.yml 中的 integration_cli 任务),因此在普通的 PR push 上按设计会被跳过。
  • 没有行内评论、议题级评论、失败的检查或持续红色的检查。差异增长窗口远低于预算(源码 2 行 / 测试 0 行,预算 400/400),无需增长审计。

已采取的行动:在本地运行指定套件,弥补已披露的缺口

本环境具备与 merge-queue 任务等效的模型凭据,因此针对从当前 HEAD 重新构建的 bundle,运行了审查中点名的那个套件:

  • 先执行 npm run build + npm run bundle(在 HEAD 上重新生成 dist/cli.js),然后运行 npm run test:integration:cli:sandbox:none
  • 结果:212 个测试 — 193 通过、18 跳过、1 失败(38 个文件:32 通过、5 跳过、1 失败)。

唯一失败的测试是环境特定的、且早已存在(在 origin/main 上会以完全相同的方式失败)

失败的测试:cli/qwen-config-dir.test.ts > 1d: CLI functions normally when QWEN_HOME is not set,在 CLI 启动阶段崩溃,报错 EACCES: permission denied, mkdir '/home/github-runner/.qwen'

证据链:

  1. 文件系统探测: 任务以 uid=1000(node) 运行,而 /home/github-runner 属主为 root:root、权限 755。直接执行 touch /home/github-runner/.qwen-write-probe 返回 Permission denied;裸运行 node -e "fs.mkdirSync('/home/github-runner/.qwen')" 返回 EACCES。在此环境中,无论代码如何,任何进程都无法创建 ~/.qwen
  2. 直接 CLI 探测: 在未设置 QWEN_HOME 的情况下运行打包后的 CLI,复现出与测试完全相同的调用栈(packages/cli/src/i18n/languageUtils.ts / gemini.tsx 中的 writeOutputLanguageFileinitializeLlmOutputLanguagemain())。
  3. 与基线逐字节一致: 两个崩溃位置文件相对 origin/maingit diff origin/main HEAD 为空;该测试在 origin/main 上也原样存在。在此环境中,基线分支会在同一位置因同一原因崩溃。
  4. 失败模式与成因完全吻合: 同一文件中的所有其他测试(以及全部 193 个通过的测试)都将 QWEN_HOME 设置为可写的临时目录;只有测试 1d 依赖不可写 home 目录下的默认 ~/.qwen
  5. 与本 PR 无关: 崩溃发生在 CLI 启动阶段,早于任何工具/权限代码运行;本 PR 变更的五个文件(ask_user_question 权限流、permissionController 的 allow 路径及其测试)均不在失败路径上。

不存在范围内的代码修复:加固 CLI 启动以应对不可写的 $HOME(或调整测试)位于本 PR 的影响范围之外,且属于维护者的决策 — 此处仅作为可能的后续事项候选记录,不予实施。在 merge queue 本身中,该任务落在具有工作区/home 属主恢复步骤的 runner 上,因此这个运行器本地的权限不匹配问题预计不会在那里复现。

本轮为何没有代码变更

审查未报告任何发现,没有需要复现的缺陷指控,唯一失败的集成测试是上述已证明的环境特定、早已存在的失败。PR 分支保持原样;未提交任何内容。

验证

  • npm run build — 通过(所有工作区包)
  • npm run bundle — 通过(在 HEAD 上重新生成 dist/cli.js
  • npm run typecheck — 通过
  • npm run lint — 通过
  • vitest run src/core/permissionFlow.test.ts src/tools/askUserQuestion.test.ts(packages/core,涉及文件)— 47 通过
  • vitest run src/nonInteractive/control/controllers/permissionController.test.ts(packages/cli,涉及文件)— 29 通过
  • npm run test:integration:cli:sandbox:none — 193 通过 / 18 跳过 / 1 失败(qwen-config-dir.test.ts 1d;环境特定且早已存在,证据见上)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants