Skip to content

fix(cli): Stop after cancelled permissions#5258

Merged
wenshao merged 2 commits into
QwenLM:mainfrom
doudouOUC:codex/fix-permission-cancel-stop
Jun 19, 2026
Merged

fix(cli): Stop after cancelled permissions#5258
wenshao merged 2 commits into
QwenLM:mainfrom
doudouOUC:codex/fix-permission-cancel-stop

Conversation

@doudouOUC

Copy link
Copy Markdown
Collaborator

What this PR does

This PR makes ACP permission cancellation stop the current turn for every tool permission request, not just ask_user_question. When a permission vote resolves as cancelled, when the reject option maps to Cancel, or when the permission request channel fails, the declined tool is recorded and later tool calls in the same model response are skipped. It applies the same fail-closed behavior to nested Agent tool permission requests so a subagent cancellation aborts the parent Agent turn instead of letting sibling or subsequent tools continue.

Why it's needed

A daemon permission timeout resolves as cancelled after the configured permission timeout. Before this change, that cancellation only stopped the turn for ask_user_question; ordinary shell, file, or MCP permission cancellations could still allow later tool calls from the same assistant step to run. This made a timed-out or explicitly cancelled permission prompt look cancelled in the UI while the model could continue executing follow-up tools.

Reviewer Test Plan

How to verify

Run the focused ACP session tests and confirm cancelled ordinary permissions, selected reject options, permission request failures, and nested subagent permission cancellations all stop the turn and skip later tool calls. For an end-to-end check, start the daemon/WebShell, create a session, set approval mode to default, prompt for two shell commands that write /tmp/qwen_cancel_first_5218 and /tmp/qwen_cancel_second_5218, then cancel the permission request; the first tool should be reported as cancelled, the second should be skipped, the turn should complete with end_turn, and neither sentinel file should exist.

Evidence (Before & After)

Before: a cancelled or timed-out non-ask_user_question permission could still let later tool calls from the same assistant step continue. After: local daemon/WebShell HTTP/SSE verification emitted one permission_request, the cancel vote returned 200, the first run_shell_command tool result was cancelled, the second tool result was skipped with the permission-cancel skip message, turn_complete.stopReason was end_turn, and both /tmp/qwen_cancel_first_5218 and /tmp/qwen_cancel_second_5218 were absent.

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

macOS local workspace with Node v22.22.3 and npm 10.9.8; daemon/WebShell E2E used QWEN_SERVER_TOKEN=codex-local-verify npm run dev:daemon -- --port 4170.

Risk & Scope

  • Main risk or tradeoff: Cancelled permission requests now stop remaining tool calls in the same model response for all permissioned tools, intentionally prioritizing the user's cancellation over continuing with model-planned follow-up tools.
  • Not validated / out of scope: Browser-panel click automation was blocked by Codex Browser policy, so the E2E validation used daemon HTTP/SSE and daemon logs; Windows and Linux were not tested locally.
  • Breaking changes / migration notes: No API or schema changes; this is a behavior fix for cancelled permission requests.

Linked Issues

Follow-up to #5218.

中文说明

What this PR does

这个 PR 让 ACP 权限取消对所有工具权限请求都会停止当前 turn,而不再只针对 ask_user_question。当权限投票结果是 cancelled、当拒绝选项映射到 Cancel、或者权限请求通道失败时,被拒绝的工具会被记录,同一个模型响应里的后续工具调用会被跳过。嵌套 Agent 工具里的权限请求也采用同样的 fail-closed 行为,因此 subagent 的权限取消会中止父级 Agent turn,而不会继续执行 sibling 或后续工具。

Why it's needed

daemon 权限超时会在配置的权限超时后解析为 cancelled。在这个改动之前,这种取消只会让 ask_user_question 停止 turn;普通 shell、file 或 MCP 权限取消仍可能让同一个 assistant step 里的后续工具继续运行。这会导致权限提示在 UI 上看起来已经取消或超时,但模型仍继续执行后续工具。

Reviewer Test Plan

How to verify

运行聚焦的 ACP session 测试,确认普通权限取消、选择拒绝选项、权限请求失败、以及嵌套 subagent 权限取消都会停止 turn 并跳过后续工具。端到端验证可以启动 daemon/WebShell,创建 session,将 approval mode 设置为 default,发送要求两个 shell command 分别写入 /tmp/qwen_cancel_first_5218/tmp/qwen_cancel_second_5218 的 prompt,然后取消权限请求;预期第一个工具显示 cancelled,第二个工具显示 skipped,turn 以 end_turn 完成,并且两个哨兵文件都不存在。

Evidence (Before & After)

Before:取消或超时的非 ask_user_question 权限请求仍可能让同一个 assistant step 里的后续工具继续执行。After:本地 daemon/WebShell HTTP/SSE 验证产生了一个 permission_request,cancel vote 返回 200,第一个 run_shell_command 工具结果为 cancelled,第二个工具结果为带 permission-cancel skip message 的 skipped,turn_complete.stopReasonend_turn,且 /tmp/qwen_cancel_first_5218/tmp/qwen_cancel_second_5218 都不存在。

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

macOS 本地工作区,Node v22.22.3,npm 10.9.8;daemon/WebShell E2E 使用 QWEN_SERVER_TOKEN=codex-local-verify npm run dev:daemon -- --port 4170

Risk & Scope

  • Main risk or tradeoff:取消的权限请求现在会对所有需要权限的工具停止同一个模型响应中的剩余工具调用,这是有意优先尊重用户取消而不是继续执行模型规划的后续工具。
  • Not validated / out of scope:浏览器面板点击自动化被 Codex Browser 策略阻止,因此 E2E 验证使用 daemon HTTP/SSE 和 daemon 日志;Windows 和 Linux 没有在本地测试。
  • Breaking changes / migration notes:没有 API 或 schema 变更;这是对权限取消行为的修复。

Linked Issues

Follow-up to #5218.

Comment thread packages/cli/src/acp-integration/session/Session.ts
Comment thread packages/cli/src/acp-integration/session/Session.test.ts
@doudouOUC
doudouOUC force-pushed the codex/fix-permission-cancel-stop branch from 624e947 to 3d1dafc Compare June 18, 2026 01:56
@wenshao

wenshao commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@wenshao

wenshao commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

✅ Local runtime verification — fix confirmed

I verified this PR locally with real test runs under tmux, driving the actual production Session code (runToolCalls turn-orchestration) via a git worktree at the PR head with an A/B against the base. Verdict: the fix is correct and the new tests genuinely catch the regression — safe to merge.

The behavior change

Before: only ask_user_question cancellation set the turn-stop flag. For any other permissioned tool (shell/file/MCP), a cancelled — or daemon-timed-out → cancelled — permission returned stopAfterUserQuestionCancel: false, so later tool calls in the same model response still ran. After: any Cancel outcome (including rejectCancel, a thrown requestPermission, or a failed cancellation onConfirm) calls onStopAfterPermissionCancel and returns stopAfterPermissionCancel: true, so later tools are skipped with the permission-cancel skip message; nested subagent permission cancellation is generalized the same way (onPermissionCancel fires for any tool, fail-closed).

How I verified

Worktree at PR head 3d1dafcca (its base 5c87b5649 postdates my local checkout, so the surrounding tree — e.g. midTurnUserMessage.ts — only exists there); node_modules symlinked from the main repo. For each A/B I swap only the two source files (Session.ts, SubAgentTracker.ts) between base and PR and keep the PR's tests.

1) PR shipped state — green

Session.test.ts + SubAgentTracker.test.ts on PR source: 187 passed (159 + 28), 0 failed.

2) The new tests are genuine regression tests (base ✗)

PR test files against base source: 25 failed / 162 passed. Those 25 are the 10 new permission-cancellation tests (non-question cancel / request-failure / reject-option / confirm-cleanup-failure / nested-subagent variants, + the 3 SubAgentTracker ones) plus 15 pre-existing tests the PR updated to the renamed field/message (stopAfterUserQuestionCancelstopAfterPermissionCancel, new skip text). All 10 new behavioral tests fail on base.

3) Rename-agnostic behavioral isolation (the load-bearing proof)

Because the renamed-field assertion trips first on base, I added one probe that asserts only the tool-execution side effects (never the renamed field/message) and ran it on both sources — driving the real session.runToolCalls([shell(cancelled), read_file]):

Source later read_file tool executed? probe
base YESexecute spy "been called 1 times" ✗ FAIL
PR no — skipped ✓ PASS

This is the exact fix, isolated from the rename: on base a non-question permission cancel lets the next tool run; on the PR it is skipped.

4) Typecheck — no new type errors

After npm run generate + building core/acp-bridge in the worktree, base and PR source produce the identical error set (diff empty). The residual errors are pre-existing worktree acp-bridge dist-staleness in files the PR doesn't touch (e.g. the Session.ts:102 MID_TURN_QUEUE_DRAIN_METHOD import is outside the PR's diff). PR 5258 adds zero type errors.

Notes for the maintainer (non-blocking)

  • Verification level: I exercised the real Session.runToolCalls turn loop with a faithful ACP-client + tool-registry mock (the project's own integration harness), plus the behavioral probe above — this directly runs the changed orchestration. I did not re-run the full daemon HTTP/SSE E2E from the test plan; the PR author's macOS daemon evidence (one permission_request, first tool cancelled, second skipped, end_turn, sentinels absent) is consistent with what the Session-level A/B shows.
  • base ✗ count nuance: 25 (not 10) tests fail on base because the PR also renames the result field/skip message that ~15 pre-existing tests assert on. The behavioral change itself is isolated by the probe in (3).
  • The fail-closed hardening is sound: requestPermission and the cancellation onConfirm are now wrapped so a transport failure stops the turn instead of silently continuing, and cleanupAgentToolResources() now also runs on the early-error path (covered by cleans up Agent sub-agent listeners when permission request fails before execution).

Recommendation: correct, well-tested, prioritizes the user's cancellation as intended. 👍 to merge.

Verified by running the project's ACP Session/SubAgentTracker test suites under tmux in a PR-head worktree, base↔PR source A/B, + a rename-agnostic behavioral probe on the real runToolCalls. Source diff is the ask_user_question→generalized-permission cancel change in Session.ts + SubAgentTracker.ts.

中文版(点击展开)

✅ 本地真实运行验证 —— 修复已确认

我在本地用 tmux 跑了真实测试,通过在 PR head 的 git worktree 里直接驱动真实的生产 Session 代码runToolCalls turn 编排),并与 base 做 A/B 对比。结论:修复正确,新增测试确实能捕获该回归 —— 可以合并。

行为变化

改动前:只有 ask_user_question 取消才会设置 turn-stop 标志。对于其它任何需要权限的工具(shell/file/MCP),取消(或 daemon 超时 → cancelled)的权限会返回 stopAfterUserQuestionCancel: false,于是同一个模型响应里后续的工具调用仍会执行。改动后:任何 Cancel 结果(包括 rejectCancelrequestPermission 抛错、取消时 onConfirm 失败)都会调用 onStopAfterPermissionCancel 并返回 stopAfterPermissionCancel: true,后续工具被跳过并带上 permission-cancel 跳过消息;嵌套 subagent 的权限取消也以同样方式泛化(onPermissionCancel 对任意工具触发,fail-closed)。

验证方法

在 PR head 3d1dafcca 建 worktree(其 base 5c87b5649 比我本地 checkout 更新,所以像 midTurnUserMessage.ts 这类周边文件只存在于那里);node_modules 从主仓库软链。每次 A/B 只切换两个源文件Session.tsSubAgentTracker.ts)在 base 与 PR 之间,测试文件保持 PR 版。

1) PR 完整状态 —— 全绿

Session.test.ts + SubAgentTracker.test.ts 在 PR 源码上:187 通过(159 + 28),0 失败。

2) 新测试是真正的回归测试(base 失败)

PR 测试文件跑 base 源码25 失败 / 162 通过。 这 25 个 = 10 个新增的权限取消测试(非 question 取消 / 请求失败 / reject 选项 / confirm 清理失败 / 嵌套 subagent 等,外加 3 个 SubAgentTracker加上 15 个被 PR 更新了字段名/消息文案的既有测试stopAfterUserQuestionCancelstopAfterPermissionCancel、新跳过文案)。10 个新行为测试在 base 上全部失败。

3) 与重命名无关的行为隔离(关键证据)

由于重命名字段的断言在 base 上会先失败,我额外加了一个探针,只断言工具执行的副作用(完全不引用被重命名的字段/消息),并在两个源码版本上运行 —— 直接驱动真实的 session.runToolCalls([shell(取消), read_file])

源码 后续 read_file 工具是否执行? 探针
base —— execute spy「被调用 1 次」 ✗ 失败
PR —— 被跳过 ✓ 通过

这就是该修复本身,且与重命名无关:在 base 上,非 question 权限取消会让下一个工具继续执行;在 PR 上它被跳过。

4) 类型检查 —— 无新增类型错误

在 worktree 里执行 npm run generate 并构建 core/acp-bridge 后,base 与 PR 源码产生完全相同的错误集合(diff 为空)。剩余错误是 worktree 里 acp-bridge dist 过期导致的既有问题,且都在 PR 未改动的文件中(例如 Session.ts:102MID_TURN_QUEUE_DRAIN_METHOD 导入不在 PR diff 内)。PR 5258 没有引入任何类型错误。

给维护者的提示(不阻塞)

  • 验证层级: 我用项目自身的集成 harness(真实 Session.runToolCalls + 忠实的 ACP-client/工具注册表 mock)跑通了被改动的编排逻辑,外加上面的行为探针 —— 这直接运行了被改的代码。我没有重跑测试计划里完整的 daemon HTTP/SSE E2E;PR 作者的 macOS daemon 证据(一个 permission_request、第一个工具 cancelled、第二个 skipped、end_turn、哨兵文件不存在)与 Session 层 A/B 结果一致。
  • base 失败 数量说明: base 上失败 25 个(而非 10 个),是因为 PR 还重命名了约 15 个既有测试所断言的结果字段/跳过消息。行为变化本身由 (3) 的探针单独隔离证明。
  • fail-closed 加固是稳妥的:requestPermission 和取消时的 onConfirm 现在都被包裹,传输失败会停止 turn 而非静默继续;cleanupAgentToolResources() 现在也会在 early-error 路径执行(由 cleans up Agent sub-agent listeners when permission request fails before execution 覆盖)。

建议: 正确、测试充分,按设计优先尊重用户的取消。👍 可以合并。

通过在 PR-head worktree 下用 tmux 跑项目的 ACP Session/SubAgentTracker 测试套件、base↔PR 源码 A/B,外加在真实 runToolCalls 上的与重命名无关行为探针完成验证。源码 diff 即 Session.ts + SubAgentTracker.tsask_user_question→泛化 permission 取消的改动。

@doudouOUC
doudouOUC marked this pull request as ready for review June 19, 2026 01:48
Copilot AI review requested due to automatic review settings June 19, 2026 01:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@wenshao

wenshao commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR, @doudouOUC!

Template looks good ✓

On direction: this is a clear safety/correctness fix and a natural follow-up to #5218. When a user cancels a permission prompt (or it times out), the model should not silently continue executing subsequent tools — that violates the user's intent and creates a confusing experience where the UI shows "cancelled" but tools still run. This is squarely within the project's core mission around safe tool permissions. CHANGELOG has #5218 as the direct predecessor — this PR completes the work by removing the ask_user_question-only guard.

On approach: the scope feels right. The core change is removing a toolName === ToolNames.ASK_USER_QUESTION guard and generalizing the rename from stopAfterUserQuestionCancel to stopAfterPermissionCancel. The added error handling for requestPermission failures and onConfirm failures are part of the same "fail closed" principle. The cleanupAgentToolResources() extraction is a genuine dedup — the same cleanup was copy-pasted in error and success paths. No unrelated changes or scope creep.

Moving on to code review. 🔍

中文说明

感谢贡献,@doudouOUC

模板完整 ✓

方向:这是一个明确的安全/正确性修复,也是 #5218 的自然后续。当用户取消权限提示(或超时)时,模型不应该静默继续执行后续工具——这违反了用户意图,也会导致 UI 显示"已取消"但工具仍在运行的混乱体验。这完全符合项目围绕安全工具权限的核心使命。CHANGELOG 中有 #5218 作为直接前驱——本 PR 通过移除 ask_user_question 的限定条件来完善这项工作。

方案:范围合理。核心改动是移除 toolName === ToolNames.ASK_USER_QUESTION 的条件判断,并将 stopAfterUserQuestionCancel 重命名为更通用的 stopAfterPermissionCancel。为 requestPermission 失败和 onConfirm 失败新增的错误处理属于同一个"关闭失败"原则。cleanupAgentToolResources() 的提取是真正的去重——相同的清理逻辑在错误和成功路径中重复出现。没有无关改动或范围蔓延。

进入代码审查 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

The change is straightforward and well-contained. Four files, all in acp-integration/session/ — the permission handling layer.

What it does: Removes the toolName === ToolNames.ASK_USER_QUESTION guard so that cancelling ANY permission (shell, file, MCP, etc.) stops the turn and skips subsequent tool calls. Previously only ask_user_question cancellations triggered this behavior.

Independent proposal vs PR approach: My independent proposal matched the PR almost exactly — remove the tool name guard, rename stopAfterUserQuestionCancelstopAfterPermissionCancel, apply in SubAgentTracker, add try/catch for requestPermission failures. The PR goes slightly further with cleanupAgentToolResources() (genuine dedup of cleanup code that was copy-pasted in error and success paths) and error handling for onConfirm failures (correct fail-closed behavior).

No critical issues found. The rename is complete (zero stale references to stopAfterUserQuestionCancel or ASK_USER_QUESTION_CANCEL_SKIP_MESSAGE in the codebase). Error handling in the new requestPermission catch block is correct — it calls onConfirm(Cancel) to notify the tool, then stops the turn. The onConfirm catch for cancellation failures is also correct — logs and returns stopAfterPermissionCancel() rather than letting it bubble.

One minor note: the onConfirm error in the cancel path is swallowed (logged but not re-thrown). This is intentional — if the user cancelled, we want to stop the turn regardless of whether onConfirm succeeds. Good judgment.

Tests

Unit tests (PR code):

  • Session.test.ts: 159 passed (0 failed) — includes new tests for non-question permission cancel, reject option cancel, onConfirm cleanup failure, permission request failure, and nested non-question permission cancel
  • SubAgentTracker.test.ts: 28 passed (0 failed) — includes updated tests verifying all tool types (not just ask_user_question) trigger the cancel callback

Typecheck: Clean across all packages (cli, core, sdk, webui).

Tmux Smoke Test

$ npm run dev -- -p 'echo hello from PR 5258'

> @qwen-code/[email protected] dev
> node scripts/dev.js -p echo hello from PR 5258

DEV is set to true, but the React DevTools server is not running.
Start it with:
$ npx react-devtools

Hello! How can I help you with PR #5258?

CLI starts and responds normally. The specific permission cancellation behavior requires the daemon/WebShell setup described in the PR's test plan (QWEN_SERVER_TOKEN=codex-local-verify npm run dev:daemon -- --port 4170), which is not available in this CI environment. The behavior is thoroughly covered by the 187 passing unit tests including dedicated tests for each cancellation scenario (non-question cancel, reject option, onConfirm failure, permission request failure, nested permissions).

中文说明

代码审查

改动直接且集中。4 个文件都在 acp-integration/session/ 中——权限处理层。

做了什么: 移除了 toolName === ToolNames.ASK_USER_QUESTION 的条件判断,使得取消任何权限(shell、file、MCP 等)都会停止 turn 并跳过后续工具调用。之前只有 ask_user_question 取消才会触发此行为。

独立方案 vs PR 方案: 我的独立方案与 PR 几乎完全一致——移除工具名称判断,重命名 stopAfterUserQuestionCancelstopAfterPermissionCancel,在 SubAgentTracker 中应用,为 requestPermission 失败添加 try/catch。PR 额外做了 cleanupAgentToolResources()(真正的去重,错误和成功路径中重复的清理代码)和 onConfirm 失败的错误处理(正确的关闭失败行为)。

未发现关键问题。 重命名完整(代码库中零残留的 stopAfterUserQuestionCancelASK_USER_QUESTION_CANCEL_SKIP_MESSAGE 引用)。新 requestPermission catch 块中的错误处理正确——调用 onConfirm(Cancel) 通知工具,然后停止 turn。取消失败时的 onConfirm catch 也正确——记录日志并返回 stopAfterPermissionCancel() 而不是让它冒泡。

一个小注意:取消路径中的 onConfirm 错误被吞掉(记录日志但不重新抛出)。这是有意的——如果用户取消了,无论 onConfirm 是否成功,我们都希望停止 turn。判断正确。

测试

单元测试(PR 代码):

  • Session.test.ts159 通过(0 失败)——包含非问题权限取消、拒绝选项取消、onConfirm 清理失败、权限请求失败、嵌套非问题权限取消等新测试
  • SubAgentTracker.test.ts28 通过(0 失败)——更新测试验证所有工具类型(不仅是 ask_user_question)触发取消回调

类型检查: 所有包(cli、core、sdk、webui)均通过。

Tmux 冒烟测试

$ npm run dev -- -p 'echo hello from PR 5258'

> @qwen-code/[email protected] dev
> node scripts/dev.js -p echo hello from PR 5258

DEV is set to true, but the React DevTools server is not running.
Start it with:
$ npx react-devtools

Hello! How can I help you with PR #5258?

CLI 正常启动并响应。具体的权限取消行为需要 PR 测试计划中描述的 daemon/WebShell 设置(QWEN_SERVER_TOKEN=codex-local-verify npm run dev:daemon -- --port 4170),本 CI 环境中不可用。该行为已被 187 个通过的单元测试充分覆盖,包括每种取消场景的专用测试。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Reflection

This is a clean, well-scoped behavior fix that closes a real safety gap. The predecessor PR (#5218) stopped the turn only for ask_user_question cancellations — this PR generalizes it to all permissioned tools, which is the correct and expected behavior. If a user cancels a shell command permission, subsequent tools in the same turn should not silently continue executing.

The implementation is minimal and focused: remove a tool name guard, rename for generality, add fail-closed error handling for requestPermission and onConfirm failures. The cleanupAgentToolResources() extraction is a genuine improvement that eliminates duplicated cleanup code. No scope creep, no drive-by refactors.

The test coverage is thorough — 187 tests pass, with dedicated tests for each cancellation scenario (non-question cancel, reject option, onConfirm failure, permission request failure, nested permissions). Typecheck is clean. The CLI smoke test confirms it builds and runs.

I'd approve this. It solves a real user problem, the approach matches what I'd independently propose, and the code is maintainable.

Verdict: Approve

中文说明

反思

这是一个干净、范围明确的行为修复,填补了一个真实的安全缺口。前驱 PR(#5218)仅对 ask_user_question 取消停止 turn——本 PR 将其推广到所有需要权限的工具,这是正确且符合预期的行为。如果用户取消了一个 shell 命令的权限,同一 turn 中的后续工具不应该静默继续执行。

实现是最小化且集中的:移除工具名称判断,重命名以反映通用性,为 requestPermissiononConfirm 失败添加关闭失败的错误处理。cleanupAgentToolResources() 提取是一个真正的改进,消除了重复的清理代码。没有范围蔓延,没有顺手重构。

测试覆盖充分——187 个测试通过,每种取消场景(非问题取消、拒绝选项、onConfirm 失败、权限请求失败、嵌套权限)都有专用测试。类型检查通过。CLI 冒烟测试确认可以构建和运行。

我会批准这个 PR。它解决了一个真实的用户问题,方案与我独立提出的方案一致,代码可维护。

结论:批准

Qwen Code · qwen3.7-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.

LGTM, looks ready to ship. ✅

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

⚠️ Downgraded from Approve to Comment: CI failing: triage. No high-confidence critical or suggestion findings beyond one inline test-coverage note below. — qwen3.7-max via Qwen Code /review

answers: output.answers,
});
} catch (error) {
if (outcome !== ToolConfirmationOutcome.Cancel) {

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] The non-Cancel re-throw branch here (if (outcome !== ToolConfirmationOutcome.Cancel) { throw error; }) has no test coverage. The only test that mocks confirmationDetails.onConfirm to reject ('skips later tools when cancellation confirmation cleanup fails' at Session.test.ts:6598) always pairs it with outcome: 'cancelled', so the re-throw path — which is the behaviorally important case where the user chose a proceed option but the cleanup callback itself failed — is untested. A follow-up test should mock requestPermission to return a proceed outcome (e.g. ProceedOnce) and mock onConfirm to reject, then assert that runToolCalls itself rejects (rather than returning a stopAfterPermissionCancel result). Without that assertion, a future refactor that accidentally swallows the error here would go undetected.

— qwen3.7-max via Qwen Code /review

@wenshao
wenshao merged commit ca1ab06 into QwenLM:main Jun 19, 2026
50 of 51 checks passed
const ASK_USER_QUESTION_CANCEL_SKIP_MESSAGE =
'Skipped because ask_user_question was cancelled before the user answered; user input is required before continuing.';
const PERMISSION_CANCEL_SKIP_MESSAGE =
'Skipped because a permission request was cancelled before the user answered; user input is required before continuing.';

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] Now that the turn stops on any cancelled permission (not just ask_user_question), this skip message is attached to cases where "before the user answered" is inaccurate:

  • A deliberate decline — ToolConfirmationOutcome.Cancel is the only Deny/Reject outcome, so the user did answer. The new test skips later tools after selecting the reject permission option records exactly this text on an explicit reject.
  • A transient requestPermission failure (the catch ~L3765 also stops the turn and reuses this message for the later tools) — no user is involved at all.

The text is model-facing (it lands in each skipped call's functionResponse.error) and goes into the recorded transcript, so the wrong cause can mislead the model and anyone debugging the session.

Suggested change
'Skipped because a permission request was cancelled before the user answered; user input is required before continuing.';
'Skipped because a prior permission request was cancelled, declined, or could not be completed; user input is required before continuing.';
中文

[建议] 现在只要任意权限请求被取消(不再限于 ask_user_question)就会终止整个回合,这条信息会被附加到两类“用户其实已经应答”的场景上:

  • 用户主动拒绝——ToolConfirmationOutcome.Cancel 是唯一的“拒绝”结果,所以用户确实应答了。新测试 skips later tools after selecting the reject permission option 正好在显式拒绝时记录了这条文本。
  • requestPermission 的瞬时失败(~L3765 的 catch 同样会终止回合,并对后续工具复用这条信息)——此时根本没有用户参与。

这条文本是面向模型的(会写入每个被跳过调用的 functionResponse.error),也会进入录制的对话记录,因此错误的原因描述会误导模型以及排查会话的人。建议改写措辞。

— claude-opus-4-8 via Claude Code /qreview

'Skipped because a permission request was cancelled before the user answered; user input is required before continuing.',
});
expect(failedPermissionExecute).not.toHaveBeenCalled();
expect(laterExecute).not.toHaveBeenCalled();

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 test verifies the permission-failure error message, the skip message, and that neither execute function ran — but it doesn't assert that onConfirm(ToolConfirmationOutcome.Cancel) was called. The production code at Session.ts:3771 calls confirmationDetails.onConfirm(Cancel) as a compensating step when requestPermission fails. The neighboring test "skips later tools when cancellation confirmation cleanup fails" does assert this.

Without this assertion, a regression that removes or skips the compensating onConfirm(Cancel) call would go undetected for non-Agent tool types.

Suggested change
expect(laterExecute).not.toHaveBeenCalled();
expect(failedPermissionExecute).not.toHaveBeenCalled();
expect(laterExecute).not.toHaveBeenCalled();
expect(onConfirm).toHaveBeenCalledWith(
core.ToolConfirmationOutcome.Cancel,
);

(This requires passing an explicit onConfirm spy to mockConfirmingTool in the test setup above.)

— qwen3.7-max via Qwen Code /review

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