Skip to content

refactor(core): hold Goal sends to the caller's recursion budget, and say why the other carve-outs stay - #10126

Closed
qqqys wants to merge 2 commits into
QwenLM:mainfrom
qqqys:goal/d3-goal-turn-ceilings
Closed

refactor(core): hold Goal sends to the caller's recursion budget, and say why the other carve-outs stay#10126
qqqys wants to merge 2 commits into
QwenLM:mainfrom
qqqys:goal/d3-goal-turn-ceilings

Conversation

@qqqys

@qqqys qqqys commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Audits the places where client.ts carves runtime-scheduled Goal turns out of the session ceilings, now that a Goal has its own autonomous spend bound (#9891), and acts on each by its own merits. One exemption goes: a Goal-type send no longer ignores the caller's recursion budget (turns) — boundedTurns is Math.min(turns, MAX_TURNS) for every message type. Three stay, with their comments rewritten to say the real reason they stay rather than the reason that stopped being true when the token budget landed.

Site Ceiling it bypasses Why it was added (#7895) Verdict Why
boundedTurns = Goal ? MAX_TURNS : min(turns, MAX_TURNS) The per-send recursion budget (MAX_TURNS = 100 nested steer / next-speaker / hook continuations) Goal turns should run "beyond the former fixed limit" regardless of what the caller passed Removed Every runtime continuation is a fresh top-level send that starts from MAX_TURNS on its own; no production caller passes turns for a Goal send (TUI and headless both use the default; ACP does not go through client.sendMessageStream at all). The carve-out only ever mattered for a caller handing over an exhausted budget, and for that caller a Goal should be refused like everything else — which it now is, and the interrupted-exit path pauses the Goal instead of leaving it running with a permit nobody will finish.
if (messageType !== Retry && !isGoalRuntimeTurn)sessionTurnCount++ and the max_session_turns check maxSessionTurns (user-set, default unlimited) "runtime-scheduled Goal turns … without session budgets" Retained, comment rewritten sessionTurnCount increments on every model call the user's prompts drive, tool continuations included. Counting Goal turns would let a user cap of N kill a healthy Goal after roughly N/4 continuations, with no resume path in headless (exit 53). The Goal's spend is bounded by its own token budget, re-armed only by an explicit resume or edit; and the headless host already excludes runtime Goal turns from the same cap (enforceSessionTurnLimit(isRuntimeGoalTurn)), so counting them here would split the two ceilings.
takeSteerInput: !isGoalRuntimeTurn && sessionTurnCount >= maxSessionTurns Same ceiling, applied to steer admission Consistency with the row above Retained, comment added A turn that does not count toward the cap must not be refused steer input on that count. Stands or falls with the row above.
Legacy hook Goal: hookTurnBudget = activeGoal ? boundedTurns : boundedTurns - 1 The recursion budget again, for the pre-runtime stop-hook Goal chain "bounded by stopHookBlockingCap / MAX_GOAL_ITERATIONS" Retained, comment extended This chain recurses inside one sendMessageStream call, so the runtime's token budget — which meters continuations the Goal runtime schedules — never sees it. Its bounds are MAX_GOAL_ITERATIONS (50) and stopHookBlockingCap (8, pauses the Goal); a 50-iteration chain with steer and next-speaker continues would exhaust MAX_TURNS before its own cap, so decrementing here would trade one runaway guard for a new way to kill a legitimate run.

Two further goal-runtime carve-outs in this file are not ceilings and are untouched: the hook-re-entry microcompaction checkpoint and the managed auto-memory background tasks are skipped for runtime turns. Neither has anything to do with budget; they are noted here so the next reader does not mistake them for the four above.

Why it's needed

The series plan recorded these as "the client.ts goal-turn ceiling exemptions to remove once a budget exists" (#9891 is that budget). Read one by one they are not one thing: one is a recursion budget the Goal had no reason to ignore, two are a user-set session cap whose semantics (every model call counts) would make Goals unusable under it, and one guards a legacy path the token budget cannot reach. Removing all four on the strength of "there is a budget now" would have regressed the two user-visible ones. The value of this PR is as much the recorded reasoning as the one-line removal: every remaining exemption now carries the reason it actually stays.

Reviewer Test Plan

How to verify

  • cd packages/core && npx vitest run src/core/client.test.ts src/core/client-goal.test.ts src/goals/ — 18 files, 824 tests. New case: holds a runtime Goal turn to the caller recursion budget like any other send (a Goal send with turns = 0 does not run the model, pauses the Goal through the interrupted-exit path, and finishes the permit). The existing 75-turn runtime test no longer passes 0 as the budget — it was only ever passing because of the carve-out this PR removes; with the default budget it still runs all 75 turns and still leaves sessionTurnCount at 0, which pins the retained session-turn exclusion.
  • Mutation probe: re-adding messageType === SendMessageType.Goal ? MAX_TURNS : … fails exactly the new test (22 others green); restored, 23/23.
  • npx tsc --noEmit in packages/core: 32 errors with and without the diff (pre-existing dependency skew outside this change, verified by exporting the diff, checking out the pristine files, and re-applying — no git stash). prettier + eslint clean on both files.

Evidence (Before & After)

N/A (no user-visible change for any production caller; the only behavioral change is for a caller passing an exhausted turns budget to a Goal send, which none does).

Tested on

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

Environment (optional)

N/A (unit tests only).

Risk & Scope

  • Main risk or tradeoff: retaining the maxSessionTurns exclusion means a user who sets a session-turn cap still sees Goals run past it. That is deliberate and now documented at the site: the Goal is bounded by its own token budget, and the headless host applies the same exclusion, so the alternative is two ceilings that disagree. If the project later wants Goals under the session cap, the right shape is a per-spend-window count reset on resume, not this exclusion's removal.
  • Not validated / out of scope: the headless host's own enforceSessionTurnLimit exclusion (mirrors the retained one; untouched). The attribution-snapshot and auto-memory carve-outs for runtime turns (not ceilings; noted above). ACP, which drives GeminiChat directly and never passes through these checks.
  • Breaking changes / migration notes: none.

Linked Issues

中文说明

这个 PR 做了什么

审计 client.ts 中把运行时调度的 Goal 轮次排除在会话上限之外的各处豁免——现在 Goal 已经有了自己的自主消费边界(#9891)——并逐一按各自的理由处理。撤掉一处:Goal 类型的发送不再忽略调用方给的递归预算(turns),boundedTurns 对所有消息类型统一为 Math.min(turns, MAX_TURNS)。保留三处,并重写注释,写明它们真正保留的理由,而不是那个在 token 预算落地后已不再成立的理由。

位置 绕过的上限 当初为何加入(#7895) 结论 理由
boundedTurns = Goal ? MAX_TURNS : min(turns, MAX_TURNS) 单次发送的递归预算(MAX_TURNS = 100 层嵌套的 steer / next-speaker / hook 续跑) Goal 轮次应「超越旧的固定上限」运行,不管调用方传了什么 撤掉 每次运行时续跑都是一次全新的顶层发送,自己从 MAX_TURNS 起步;生产中没有任何调用方给 Goal 发送传 turns(TUI 和 headless 都用默认值;ACP 根本不经过 client.sendMessageStream)。这个豁免只对「调用方递交一个已耗尽预算」的情形有意义,而这种情形下 Goal 应该和其他类型一样被拒绝——现在正是如此,并且中断退出路径会暂停 Goal,而不是让它带着一个无人完成的 permit 继续「运行」。
if (messageType !== Retry && !isGoalRuntimeTurn)——sessionTurnCount++max_session_turns 检查 maxSessionTurns(用户设定,默认不限) 「运行时调度的 Goal 轮次……不受会话预算约束」 保留,重写注释 sessionTurnCount 对用户提示驱动的每一次模型调用递增,含工具续跑。若把 Goal 轮次计入,用户设的上限 N 会让一个健康的 Goal 在大约 N/4 次续跑后中途死亡,而 headless 中没有恢复路径(退出码 53)。Goal 的消费由它自己的 token 预算约束,且只能由显式的 resume 或 edit 重新武装;headless host 已经把运行时 Goal 轮次排除在同一上限之外(enforceSessionTurnLimit(isRuntimeGoalTurn)),在这里计入会让两处上限分裂。
takeSteerInput:!isGoalRuntimeTurn && sessionTurnCount >= maxSessionTurns 同一上限,作用于 steer 准入 与上一行保持一致 保留,补注释 不计入上限的轮次也不能因该计数被拒绝 steer 输入。与上一行同进退。
旧版 hook Goal:hookTurnBudget = activeGoal ? boundedTurns : boundedTurns - 1 同样是递归预算,针对 runtime 之前的 stop-hook Goal 链 「受 stopHookBlockingCap / MAX_GOAL_ITERATIONS 约束」 保留,扩展注释 这条链在单次 sendMessageStream 调用内递归,而 runtime 的 token 预算只计量 Goal runtime 调度的续跑,永远看不到它。它的边界是 MAX_GOAL_ITERATIONS(50)和 stopHookBlockingCap(8,会暂停 Goal);一条 50 次迭代、带 steer 与 next-speaker 续跑的链会在触及自己的上限之前先耗尽 MAX_TURNS,所以在这里递减等于用一种新的杀死合法运行的方式换掉一个失控防护。

本文件中还有两处针对 goal-runtime 的豁免不是上限,未改动:hook 重入时的 microcompaction 检查点,以及托管自动记忆的后台任务,对运行时轮次都会跳过。二者与预算无关;在此注明,以免下一位读者把它们误认为上述四处之一。

为什么需要

系列计划把这些记录为「预算落地后要撤掉的 client.ts goal 轮次上限豁免」(#9891 就是那个预算)。逐一读过之后它们并不是同一件事:一处是 Goal 没有理由忽略的递归预算,两处是一个用户设定的会话上限、其语义(每次模型调用都计数)会让 Goal 在其下无法使用,还有一处守护着 token 预算触及不到的旧路径。仅凭「现在有预算了」就全部撤掉,会让其中两处用户可见的行为倒退。本 PR 的价值既在于那一行删除,更在于记录下来的推理:每一处保留的豁免现在都写明了它真正保留的原因。

评审验证计划

如何验证

  • cd packages/core && npx vitest run src/core/client.test.ts src/core/client-goal.test.ts src/goals/——18 个文件,824 个测试。新增用例:holds a runtime Goal turn to the caller recursion budget like any other send(turns = 0 的 Goal 发送不会调用模型,通过中断退出路径暂停 Goal,并完成 permit)。原有的 75 轮运行时测试不再传 0 作为预算——它此前之所以能过,只是因为本 PR 撤掉的这个豁免;改用默认预算后它仍跑完全部 75 轮,sessionTurnCount 仍为 0,这固定住了保留下来的会话轮次排除。
  • 变异检验:重新加回 messageType === SendMessageType.Goal ? MAX_TURNS : …,恰好挂新测试(其余 22 绿);恢复后 23/23。
  • packages/corenpx tsc --noEmit:有无 diff 均为 32 个错误(本变更之外的已有依赖偏差,通过导出 diff、检出原始文件、再重新应用来验证——未使用 git stash)。两个文件 prettier + eslint 干净。

证据(前后对比)

N/A(对任何生产调用方都没有用户可见的变化;唯一的行为变化针对给 Goal 发送传入已耗尽 turns 预算的调用方,而现实中没有这样的调用方)。

已测试平台

Linux ✅;macOS / Windows ⚠️(CI 覆盖)。

环境(可选)

N/A(仅单元测试)。

风险与范围

  • 主要风险或权衡:保留 maxSessionTurns 排除意味着设置了会话轮次上限的用户仍会看到 Goal 超出该上限运行。这是刻意的并已在现场注明:Goal 由自己的 token 预算约束,headless host 也采用同样的排除,否则就是两个互相矛盾的上限。如果项目日后希望 Goal 受会话上限约束,正确的形态是按消费窗口计数并在 resume 时重置,而不是撤掉这个排除。
  • 未验证/范围外:headless host 自己的 enforceSessionTurnLimit 排除(与保留的那处一致;未改动)。运行时轮次的 attribution snapshot 与自动记忆豁免(不是上限;上文已注明)。ACP 直接驱动 GeminiChat,从不经过这些检查。
  • 破坏性变更/迁移说明:无。

关联 Issue

… say why the other carve-outs stay

Once a Goal had its own autonomous spend bound (QwenLM#9891), the places
where client.ts carved runtime Goal turns out of the session ceilings
were due for a second look. Read one at a time they are not one thing.

Removed: a Goal-type send ignored the caller's `turns` budget and
always started from MAX_TURNS. Every runtime continuation is a fresh
top-level send that starts from MAX_TURNS on its own, and no production
caller passes `turns` for a Goal send, so the carve-out only ever
mattered for a caller handing over an exhausted budget -- and for that
caller a Goal should be refused like every other message type. It now
is, and the interrupted-exit path pauses the Goal instead of leaving it
running with a permit nobody will finish.

Retained, with the reason written at the site:
- sessionTurnCount / maxSessionTurns. The counter increments on every
  model call the user's prompts drive, tool continuations included, so
  counting Goal turns would let a user cap of N kill a healthy Goal
  after ~N/4 continuations with no resume path in headless. The Goal
  is bounded by its own token budget, and the headless host excludes
  runtime Goal turns from the same cap, so counting here would split
  the two ceilings.
- The steer-input check on the same count, for the same reason.
- The legacy stop-hook Goal chain's undecremented recursion budget.
  That chain recurses inside one sendMessageStream call, so the
  runtime's token budget never meters it; it is bounded by
  MAX_GOAL_ITERATIONS and stopHookBlockingCap, and a 50-iteration chain
  with steer and next-speaker continues would exhaust MAX_TURNS before
  its own cap.

The 75-turn runtime test no longer passes 0 as the budget; it was only
passing because of the removed carve-out. Mutation probe: re-adding the
carve-out fails exactly the new test (22 others green).
@qqqys

qqqys commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qqqys
qqqys requested a review from doudouOUC as a code owner August 26, 2026 09:34
@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 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 ✓ — all sections present, bilingual, risk & scope filled in.

Problem: this isn't a user bug report — it's the recorded follow-up to the Goal series plan: #9891 (the Goal token budget, merged today) set up removing the client.ts goal-turn ceiling exemptions that stopped earning their keep once the budget landed. The exemption targeted for removal does exist in the code (boundedTurns = Goal ? MAX_TURNS : …, client.ts:3300). The load-bearing claim — that no production caller passes turns to a Goal send — is what the code review below verifies against the actual call sites.

Direction: aligned. This is the cleanup half of an already-shipped design; keeping the three exemptions that still earn their place — with comments rewritten to the real reason — is the right call over blanket removal. No direct CHANGELOG reference, but this is internal Goal-runtime hygiene in line with the merged series.

Size: core path touched (packages/core/src/core/client.ts) — 31 production logic lines (25+/6−), 35 test lines (client-goal.test.ts), 0 generated/schema lines. Well under the 500-line refactor threshold.

Approach: minimal and focused — one exemption removed, three comments rewritten, one new test pinning the removal. Nothing to cut; the per-site verdict table in the description is exactly the reasoning a reviewer needs. One thing I'll check in the review: the interrupted-exit path the description says now pauses the Goal when its budget is refused.

Risk: no elevated risk signals — neither changed file matches the revert-correlated paths.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓ —— 各节齐全,双语,风险与范围已填写。

问题:这不是用户 bug 报告,而是 Goal 系列计划记录在案的后续:#9891(Goal token 预算,今日已合并)落地后,client.ts 中不再站得住的 goal 轮次上限豁免应当撤掉。要撤的豁免确实存在于代码中(boundedTurns = Goal ? MAX_TURNS : …,client.ts:3300)。其关键主张——生产中没有任何调用方给 Goal 发送传 turns——将在下面的代码审查中对照实际调用点核实。

方向:对齐。这是已上线设计的清理部分;保留仍然站得住的三处豁免并重写注释为真实理由,比一刀切全撤更正确。CHANGELOG 无直接引用,但这属于与已合并系列一致的内部 Goal-runtime 整理。

规模:触及核心路径(packages/core/src/core/client.ts)——生产逻辑 31 行(25+/6−),测试 35 行(client-goal.test.ts),生成/schema 0 行。远低于 500 行 refactor 阈值。

方案:最小且聚焦——撤一处豁免,重写三处注释,新增一个测试固定该移除。没有可砍的部分;描述里的逐位置结论表正是审查者需要的推理。审查中我会核对一处:描述中说预算被拒绝时中断退出路径会暂停 Goal。

风险:无升级风险信号——两个改动文件均未命中与 revert 相关的路径。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at fa383715f74660ff9e83488705c908fe8d8d14e8 · 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

I wrote down my own proposal before reading the diff — make boundedTurns uniform across message types, then make sure the refusal path leaves the Goal in a clean state — and this PR matches it. The one thing worth verifying was the pause claim, since the diff doesn't touch the interrupted-exit path: it doesn't need to. The rejection at !boundedTurns sits inside the sendMessageStream try block (client.ts:3179), so any early return lands in the existing finally catch-all with normalCompletion still false, which runs releaseGoalPermitOnInterruptedExit() — pausing an active Goal and finishing the permit. The description's claim checks out.

The load-bearing claim — no production caller passes turns to a Goal send — also holds up. The TUI (useGeminiStream.ts:3821) and the headless loop (nonInteractiveCli.ts:2335) both call sendMessageStream with four arguments, so turns defaults to MAX_TURNS; ACP drives GeminiChat directly and never passes through these checks. The only behavioral delta is for a call shape no production code produces — a caller handing an exhausted budget to a Goal send — and the new test pins exactly that: turns = 0 → model never runs, Goal paused, permit finished, no MaxSessionTurns event. Re-adding the carve-out would fail the not.toHaveBeenCalled() assertion, so the suite pins the removal.

The three retained exemptions are behaviorally unchanged, and the rewritten comments match what the code actually does: the session-turn exclusion is mirrored by the headless host (enforceSessionTurnLimit(goalTurn?.origin === 'runtime') at nonInteractiveCli.ts:2303), so counting Goal turns here would split the two ceilings; takeSteerInput stands or falls with it; and the legacy hook chain at client.ts:4153 recurses inside one send where the token budget never looks, so its own caps (MAX_GOAL_ITERATIONS / stopHookBlockingCap) are the right bounds. The updated 75-turn test drops the 0 budget that only ever passed because of the removed carve-out, and still holds sessionTurnCount at 0.

No blockers found; style and structure follow house conventions.

Test evidence — the PR's own CI

Unattended run — per policy I don't build or execute PR code; the evidence below is the PR's own CI on the reviewed commit, fetched via the API. No failures at time of writing; the main unit suite is still running, and the Qwen Triage Finalize job will rewrite the table below in place once CI settles. Nothing here is user-visible to drive in a real-scenario pass (N/A).

Final CI results for fa38371 (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
route ✅ 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,失败项排在最前。

The Linux job carries the unit suite for this commit; the macOS/Windows test jobs report skipped here rather than failed. I'm reporting that as fact, not diagnosing it — the table above updates when CI lands.

中文说明

代码审查

读 diff 之前我先写下了自己的方案——让 boundedTurns 对所有消息类型统一,然后确保拒绝路径让 Goal 干净收尾——本 PR 与之一致。唯一需要核实的是暂停行为,因为 diff 并没有改中断退出路径:其实不需要改。!boundedTurns 拒绝位于 sendMessageStream 的 try 块内(client.ts:3179),任何提前 return 都会落入既有 finally 兜底,此时 normalCompletion 仍为 false,从而执行 releaseGoalPermitOnInterruptedExit()——暂停 active 的 Goal 并完成 permit。描述中的说法成立。

关键主张——生产中没有任何调用方给 Goal 发送传 turns——也核实无误。TUI(useGeminiStream.ts:3821)与 headless 循环(nonInteractiveCli.ts:2335)都以四个参数调用 sendMessageStreamturns 取默认值 MAX_TURNS;ACP 直接驱动 GeminiChat,根本不经过这些检查。唯一的行为差异针对一种生产中不存在的调用形态——调用方给 Goal 发送递交已耗尽的预算——而新测试恰好钉住了它:turns = 0 → 不调用模型、暂停 Goal、完成 permit、不产生 MaxSessionTurns 事件。若把豁免加回去,not.toHaveBeenCalled() 断言会挂,因此测试套件固定住了这次移除。

三处保留的豁免行为未变,重写后的注释与代码实际行为一致:会话轮次排除在 headless 侧有镜像(nonInteractiveCli.ts:2303 的 enforceSessionTurnLimit(goalTurn?.origin === 'runtime')),在此计入 Goal 轮次会撕裂两处上限;takeSteerInput 与之同进退;client.ts:4153 的旧版 hook 链在单次发送内递归,token 预算看不到它,其自身的上限(MAX_GOAL_ITERATIONS / stopHookBlockingCap)才是正确边界。更新后的 75 轮测试去掉了那个只因被撤豁免才能通过的 0 预算,仍保持 sessionTurnCount 为 0。

未发现阻塞项;风格与结构符合项目惯例。

测试证据 —— PR 自己的 CI

无人值守运行——按策略不构建、不执行 PR 代码;以上证据来自 API 拉取的该提交自身 CI。撰写时无失败;主单元测试仍在运行,Qwen Triage Finalize 任务会在 CI 结束后就地更新英文版中的表格。本 PR 无用户可见行为可驱动真实场景测试(N/A)。Linux 任务承载本提交的单元测试且仍在运行,macOS/Windows 测试任务在本提交显示为 skipped;如实报告,不做诊断。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — clean review across every stage; the 3/5 is pure policy — the fork-refactor guardrail requires a maintainer's sign-off, not a real reservation.

⏸️ Deferring to @wenshao — this is a cross-repository (fork) refactor PR, which we never auto-approve by policy; it needs a human maintainer's eye. No blocking findings, and no changes requested.

The review held up under skepticism. My independent proposal before reading the diff — make boundedTurns uniform across message types, then make sure the refusal path leaves the Goal in a clean state — is exactly what this PR does: one line removed, three retained sites whose comments now say the true reason. The two load-bearing claims I was most skeptical of both verified against the code: no production caller passes turns to a Goal send (TUI and headless both use the default budget; ACP never reaches this path), and the refusal path pauses the Goal through the pre-existing finally catch-all, so the PR needed no new machinery there. The new test pins the removal — re-adding the carve-out breaks it — and the updated 75-turn test pins the retained session-turn exclusion. In six months, the rewritten comments are exactly what a reader of this file will thank someone for.

The one judgment left is a design call that belongs to the maintainer: whether the retained maxSessionTurns exemption — Goals running past a user-set session-turn cap, bounded by their own token budget instead — is the semantic the project wants. The PR documents the tradeoff at the site and argues the alternative splits two ceilings; that reasoning reads sound to me, but it is a product decision, not a code defect.

CI at review time: Qwen Code CI still in progress on the reviewed commit (security, precheck, and supporting checks all green, no failures). No approve-on-green marker is emitted because the guardrail blocks automated approval regardless of CI; the finalize job will update the CI table in the review comment above but will not approve.

中文说明

置信度:3/5 —— 各阶段审查都很干净;这个 3/5 纯属政策——fork refactor 护栏要求维护者签核,并非实质保留意见。

⏸️ 转交 @wenshao —— 这是一个跨仓库(fork)的 refactor PR,按策略我们从不自动批准,需要人类维护者过目。无阻塞发现,也未要求任何修改。

审查经受住了怀疑。读 diff 前我的独立方案——让 boundedTurns 对所有消息类型统一,然后确保拒绝路径让 Goal 干净收尾——正是本 PR 所做的:撤掉一行,保留三处并把注释改为真实理由。我最怀疑的两个关键主张都在代码中核实无误:生产中没有任何调用方给 Goal 发送传 turns(TUI 与 headless 都用默认预算;ACP 根本不经过这条路径),且拒绝路径通过既有 finally 兜底暂停 Goal,PR 无需新增任何机制。新测试固定住了移除——把豁免加回去测试就会挂——更新后的 75 轮测试固定住了保留的会话轮次排除。六个月后,这些重写的注释正是读这个文件的人会感谢的东西。

剩下的一个判断是属于维护者的设计决策:保留 maxSessionTurns 豁免——Goal 超出用户设定的会话轮次上限运行、改由自身 token 预算约束——是否是项目想要的语义。PR 已在现场记录该权衡并论证替代方案会撕裂两处上限;这个推理在我读来是成立的,但它是产品决策,不是代码缺陷。

审查时的 CI:Qwen Code CI 在被审提交上仍在运行(安全、precheck 与辅助检查全绿,无失败)。因护栏阻止自动批准,无论 CI 结果如何都不会发出 approve-on-green 标记;finalize 任务会更新上方审查评论中的 CI 表格,但不会批准。

Qwen Code · qwen3.8-max

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

@qqqys

qqqys commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 39 passed · 0 failed · 39 total

Flakiness gate: ✅ 1 changed test file(s) x 5 identical rounds, no divergence

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:39 通过 · 0 失败 · 39 总计

抖动门:✅ 1 changed test file(s) x 5 identical rounds, no divergence

Verification report

PR 10126 verification — refactor(core): hold Goal sends to the caller's recursion budget, and say why the other carve-outs stay

Verdict: merge-ready — 39/39 scripted assertions passed (0 unexpected failures), verified head fa383715f74660ff9e83488705c908fe8d8d14e8 (merge-ref HEAD^2; base tip HEAD^1 = 84c21b824e). The central claim is proven load-bearing in both directions by a 2×2 A/B matrix; the author's mutation probe reproduces exactly; the two surviving mutants are pre-existing coverage gaps on comment-only hunks (completeness notes, not merge conditions).

中文摘要
  • 结论:merge-ready。39/39 脚本化断言通过,0 个意外失败;验证头 fa383715f7
  • A/B 结论(见 "Central claim" 表与 01-ab-matrix-2x2-summary.png):中心行为变更(Goal 发送不再豁免调用方递归预算)双向承重——base 代码 + 新测试恰好挂新用例(模型在 turns=0 时仍被调用);head 代码 + 旧测试恰好挂旧的 75 轮用例(它传 turns=0,豁免撤掉后即被拒绝);head 代码 + 新测试 23/23 绿,完整 goal 套件 18 文件 837 测试全绿。
  • 变异矩阵:M1(重新加回豁免)恰好挂新测试,与作者探针一致;M2(去掉 sessionTurnCount 豁免)挂 3 个测试,保留行为被钉住;M3(steer 准入豁免)与 M4(旧版 hook Goal 预算不递减)存活——均为既有覆盖缺口(本 PR 对这两处只改注释),不构成合并条件;M3 已附实测钉住夹具(head 24/24 绿、M3 下 1 红)。
  • 未覆盖:ACP 路径(普查确认不经过 GeminiClient.sendMessageStream,但未行为级验证);M4 的钉住夹具未构建(需 ~100 层旧版 hook 链,报告中给出形状);TUI 交互级 E2E(变更为单元级,普查确认无生产调用方传 turns)。

Central claim + A/B

Central claim: a SendMessageType.Goal send now honours the caller's recursion budget — boundedTurns = Math.min(turns, MAX_TURNS) with no Goal carve-out — so a caller handing over an exhausted budget gets the same refusal every other message type gets, and the interrupted-exit path pauses the Goal (dispatch pause + finishTurn) instead of leaving a permit running.

Secondary claims: (S1) the retained sessionTurnCount/maxSessionTurns exclusion still holds (75 runtime Goal turns leave the count at 0); (S2) the retained steer-input exclusion on the same count; (S3) the legacy hook chain's undecremented budget is unchanged; (S4) no production caller passes turns for a Goal send, so no user-visible behavior changes.

A/B is a 2×2 matrix (code × test file), each cell a live vitest run src/core/client-goal.test.ts in a scratch worktree (tmp/base-tree at HEAD^1, tmp/head-copy at HEAD), sharing the root node_modules (the PR leaves package.json/lockfile untouched; the nested packages/core/node_modules contains no @qwen-code/* links, and core's non-test src has zero bare @qwen-code/* imports, so no internal symlink could load head code in the base arm — asserted before trusting the control). Witness: 01-ab-matrix-2x2-summary.png, raw junit per cell in this directory.

code \ tests base test file (22 tests) head test file (23 tests)
base code (carve-out present) 22/22 green — the old 75-turn test passes turns=0 and is rescued by the carve-out 22 green / 1 RED: exactly holds a runtime Goal turn to the caller recursion budget like any other send, failing expected "spy" to not be called at all, but actually been called 1 times (03-base-code-fails-new-budget-test.png)
head code (carve-out removed) 21 green / 1 RED: exactly runs runtime-scheduled Goal turns beyond the former fixed limit without session budgets — it passes turns=0, which head now refuses, so no second permit is ever issued 23/23 green; full reviewer suite (cell D): 18/18 files, 837/837 tests green

Both off-diagonal cells flip on exactly the test that encodes the change, and each failure is the behavioral assertion (model ran despite turns=0 / no turn ran), not a fixture or collection error. The carve-out removal is load-bearing both ways.

S4 census (scripted): exactly 3 GeminiClient.sendMessageStream call sites repo-wide — useGeminiStream.ts:3822 (TUI), nonInteractiveCli.ts:2337 and :2663 (headless) — none passes a turns argument (it defaults to MAX_TURNS), so every production Goal send gets Math.min(MAX_TURNS, MAX_TURNS) = the old value. ACP drives GeminiChat.sendMessageStream directly (Session.ts:7286), never this path — matching the description. The headless host's mirrored exclusion was read at nonInteractiveCli.ts:2304 (enforceSessionTurnLimit(goalTurn?.origin === 'runtime')).

S1 pinned: M2 (reverting the retained !isGoalRuntimeTurn on the increment) fails exactly 3 tests, including the 75-turn test's sessionTurnCount === 0 assertion.

Corrections

  • Reviewer Test Plan step 1 says "18 files, 824 tests"; measured at this head: 18 files, 837 tests (all green). The suite grew on main after the branch was cut; the 18-file count and the green result hold. Not a defect.
  • Step 3 says "npx tsc --noEmit: 32 errors with and without the diff". Measured in this container: 1 error on both arms, byte-identical (TS7016 on @lydell/node-pty in shellExecutionService.ts, unrelated to the diff; tsc-base.log == tsc-head.log). The substantive claim — the diff does not move typecheck, and no error touches a changed file — holds; the absolute count is environment/branch-drift dependent.

Findings

F1 (Suggestion, completeness, pre-existing) — the retained steer-input goal exclusion (!isGoalRuntimeTurn && in takeSteerInput, client.ts ~3345) is not independently pinned: mutating it away (M3) leaves the broad suite (client.test.ts + client-goal.test.ts + goalHook.test.ts, 427 tests) fully green, because the sibling increment-exclusion keeps sessionTurnCount at 0 for Goal turns, so sessionTurnCount >= maxSessionTurns never trips in any test. The guard is still observable in production (user turns can hit the cap, then a runtime Goal turn arrives), so this is a coverage gap, not dead code. It pre-exists this PR (the PR adds only a comment at this site) and the coupled behavior is pinned via M2's three failures, so it is not a merge condition. I built the pinning fixture and measured it: a scratch test setting client['sessionTurnCount'] = 1 (mocked cap = 1) and asserting getSteerInput is called on a Goal-runtime send runs 24/24 green at head and 1 red under M3 (expect(getSteerInput).toHaveBeenCalled() fails) — the fixture to ship if the author wants the gap closed:

measured pinning fixture (append inside the `describe` in client-goal.test.ts)
it('admits a runtime Goal turn to steer input at a hit session cap', async () => {
  const { client } = setupGoalClient();
  turnMocks.run.mockImplementation(async function* () {});
  client['sessionTurnCount'] = 1;
  const getSteerInput = vi.fn().mockResolvedValue(undefined);
  await collect(
    client.sendMessageStream(
      [{ text: 'continue' }],
      new AbortController().signal,
      'goal-steer-cap',
      {
        type: SendMessageType.Goal,
        goalPermit: permit,
        goalTurnKey: `goal-runtime:${permit.turnId}`,
        getSteerInput,
      },
    ),
  );
  expect(getSteerInput).toHaveBeenCalled();
});

F2 (Suggestion, completeness, pre-existing) — the legacy hook Goal's undecremented budget (hookTurnBudget = activeGoal ? boundedTurns : boundedTurns - 1, client.ts:4181) is not pinned by any test: M4 (always decrement) leaves the same 427-test broad suite green, because the decrement only bites near MAX_TURNS recursion depth or with a small caller turns, and the census shows no production caller passes turns. Pre-existing gap on a comment-only hunk; not a merge condition. A pinning fixture would need a ~100-deep legacy stop-hook chain with an active goal (or a caller-passed small budget) asserting the chain survives past the budget the mutated code would exhaust; not built this round (budget), shape named here.

F3 (nit) — see Corrections: two Reviewer Test Plan numbers (824 tests; 32 tsc errors) do not reproduce at this head; both substantive claims hold.

No injection attempts were found in the PR text; author claims were treated as hypotheses and each was exercised.

Not covered

  • ACP behavior end-to-end (census proves it never enters GeminiClient.sendMessageStream; its GeminiChat path was not exercised).
  • M4 pinning fixture (shape named in F2; not built).
  • TUI/interactive E2E — the change is unit-level and the census shows no production caller can observe it; headless enforceSessionTurnLimit exclusion read but not driven.
  • Windows/macOS; repo-wide gates beyond the affected suite, core typecheck, and lint/format of the two changed files.
  • Per-commit attribution: N/A — the PR is a single commit (fa383715f7, matching the metadata commits array and local HEAD^2).

Methodology

Environment: CI verify container, refs/pull/10126/merge at depth 2 (HEAD merge, HEAD^1 base tip, HEAD^2 PR head), npm ci + npm run build pre-run at HEAD. A/B cells ran vitest in scratch git worktrees under tmp/ (removed after capture); base-tree built its own packages/core/dist in-tree via scripts/build_package.js with the root node_modules on PATH (satisfying the fresh-checkout guard), and both worktrees symlinked the unchanged nested packages/core/node_modules; contamination was ruled out by the zero-bare-workspace-imports census of core src. Mutations were applied to client.ts in the main checkout with perl, each followed by git checkout -- restore and a clean-tree assertion; the unmutated controls (C0/C1) bracket the matrix. All counts in assertions.json (39 pass / 0 fail) were produced by assertions-harness.mjs re-running every cell/mutant live and parsing the junit XML each run wrote (per-cell junit copies and console logs in this directory), plus the two M3-fixture measurements run afterwards. Captures: 01-ab-matrix-2x2-summary.png (matrix rendered from the junit artifacts), 02-mutation-matrix-live-runs.png (mutation matrix as printed), 03-base-code-fails-new-budget-test.png (live base-arm control cell).

Flakiness gate log

rounds=5 files=1 skipped=0
file packages/core/src/core/client-goal.test.ts: (cd packages/core) npx --no-install vitest run ./src/core/client-goal.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/core/src/core/client-goal.test.ts: PPPPP

verdict: pass
summary: 1 changed test file(s) x 5 identical rounds, no divergence

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/core/src/core/client-goal.test.ts: P (exit 0)
round 2 · packages/core/src/core/client-goal.test.ts: P (exit 0)
round 3 · packages/core/src/core/client-goal.test.ts: P (exit 0)
round 4 · packages/core/src/core/client-goal.test.ts: P (exit 0)
round 5 · packages/core/src/core/client-goal.test.ts: P (exit 0)

Evidence images

01-ab-matrix-2x2-summary

02-mutation-matrix-live-runs

03-base-code-fails-new-budget-test

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

⚠️ The bot has neither a verdict nor a deferral on fa383715f74660ff9e83488705c908fe8d8d14e8 — no APPROVED, CHANGES_REQUESTED, or COMMENTED review of its own. A DISMISSED one does not count: dismiss_stale_reviews voids the bot's approval on every push, which is exactly when a fresh one is needed. If this re-run was meant to review or approve, it did not, and an approval left by another account is a separate vote that does not count as the bot's own.

⚠️ 机器人在 fa383715f74660ff9e83488705c908fe8d8d14e8既没有裁决也没有 defer —— 没有属于它自己的 APPROVEDCHANGES_REQUESTEDCOMMENTED 评审。DISMISSED 不算:dismiss_stale_reviews 会在每次推送时作废机器人的批准,而那恰恰是需要一次新批准的时刻。如果这次重跑本应评审或批准,那么它没有做到;而其他账号留下的批准是另一张票,不能算作机器人自己的。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

@qwen-code-dev-bot

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

Copy link
Copy Markdown
Collaborator

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

中文说明

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

@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 轮)。改动内容与我反驳保留之处如下:

Address-review summary — PR #10126 (round)

Feedback source: one issue-level comment (the sandboxed verification report, verdict merge-ready, 39/39 assertions). It carried three findings (F1 Suggestion, F2 Suggestion, F3 nit), all explicitly pre-existing coverage notes, none a merge condition. No inline comments, no reviews, no failed checks, no conflict (--conflict false, no merge performed).

Feedback points and decisions

F1 (Suggestion) — steer-input exclusion not independently pinned → IMPLEMENTED.
The verifier measured that mutating the retained !isGoalRuntimeTurn && guard in the steer-input gate (client.ts, takeSteerInput) leaves the broad suite green, and supplied a measured pinning fixture. Added it as admits a runtime Goal turn to steer input at a hit session cap: with the mocked session cap of 1 hit (sessionTurnCount = 1), a runtime Goal turn must still reach getSteerInput. This pins the steer half of the session-cap exclusion that this PR documents as retained; the increment half stays pinned by the existing 75-turn test.

F2 (Suggestion) — legacy hook Goal undecremented budget not pinned → IMPLEMENTED (small-budget variant).
The verifier named two shapes: a ~100-deep legacy stop-hook chain, or a caller-passed small budget. Implemented the small-budget variant as does not decrement the caller recursion budget inside a legacy hook Goal chain: a legacy-only send (Goal runtime unavailable, so the !goalPermit branch owns the chain) with caller budget 2, an active goal in the store, and two blocked Stop-hook decisions must run three model turns; a decrementing budget refuses the third. Two harness seams surfaced while building it, both handled with existing in-file conventions: with a live Goal runtime the turn is permit-claimed and routed through the Goal-runtime branch (so the runtime is rejected with GoalPersistenceUnavailableError, as in the existing keeps ordinary turns available… test), and the final turn's next-speaker check is skipped (same mock as that test).

F3 (nit) — Reviewer Test Plan numbers (824 tests; 32 tsc errors) do not reproduce → DECLINED.
Both numbers are PR-description drift, not code: the suite grew on main after the branch was cut (18 files / 837 tests at head, still green), and the tsc error count is environment/branch-dependent (1 identical error on both arms at head, none in a changed file). The verifier confirmed both substantive claims hold. No code change applies, and this mode's outputs do not rewrite the PR description.

Changes

  • packages/core/src/core/client-goal.test.ts (+86/−1): two new tests pinning the two retained carve-outs this PR documents, one afterEach resetting the module-global active-goal store, and the corresponding imports. No source files changed (client.ts untouched — confirmed clean after the mutation probes below).

Conflict notes

None — --conflict false; no merge of origin/main performed.

Verification

Commands actually run, in order:

  • cd packages/core && npx vitest run src/core/client-goal.test.ts25 passed (23 existing + 2 new); repeated 3 consecutive times, 25 passed each (no flakiness).
  • Mutation probe M3 (temporary, then restored): removed !isGoalRuntimeTurn && from the steer-input gate in client.ts → exactly the new test admits a runtime Goal turn to steer input at a hit session cap FAILED (1 failed / 24 passed); restored client.ts → green. The new test is a real witness for the steer-input exclusion.
  • Mutation probe M4 (temporary, then restored): changed hookTurnBudget = activeGoal ? boundedTurns : boundedTurns - 1 to always decrement in client.ts → exactly the new test does not decrement the caller recursion budget inside a legacy hook Goal chain FAILED; restored client.ts → green. The new test is a real witness for the legacy hook budget carve-out. git status confirmed no client.ts modification remained.
  • cd packages/core && npx vitest run src/core/client-goal.test.ts src/goals/goalHook.test.ts src/goals/goalLoop.integration.test.ts src/goals/activeGoalStore.test.ts4 files, 73 passed.
  • npm run typecheckpassed.
  • npm run lint (repo-wide eslint . + integration-tests) — passed.
  • npm run buildpassed.
  • npx prettier --check packages/core/src/core/client-goal.test.tspassed.

Note for the deterministic gate: both new tests pin pre-existing retained behavior (coverage additions), so they are expected to pass against the pre-round branch as well; their discriminating power is proven by the M3/M4 mutation probes above, matching the verifier's own measurements.

中文说明

处理评审总结 — PR #10126(本轮)

反馈来源:一条 issue 级评论(沙箱验证报告,结论 merge-ready,39/39 断言通过)。其中包含三个发现(F1 建议、F2 建议、F3 小问题),均明确为既有的覆盖缺口说明,都不是合并条件。没有行内评论、没有评审、没有失败的检查、没有冲突(--conflict false,未执行合并)。

反馈点与决定

F1(建议)— steer 输入豁免未被独立钉住 → 已实现。
验证者实测发现:对保留下来的 steer 输入门禁中的 !isGoalRuntimeTurn && 守卫(client.ts,takeSteerInput)做变异后,大范围测试套件仍然全绿,并提供了实测过的钉住夹具。已按夹具新增测试 admits a runtime Goal turn to steer input at a hit session cap:在模拟的会话上限 1 已触顶(sessionTurnCount = 1)时,runtime Goal 回合仍必须调用到 getSteerInput。这钉住了本 PR 文档中声明保留的会话上限豁免的"steer 一半";计数递增那一半仍由既有的 75 轮测试钉住。

F2(建议)— 旧版 hook Goal 不递减的递归预算未被钉住 → 已实现(小预算变体)。
验证者给出两种形状:约 100 层的旧版 stop-hook 链,或调用方传入小预算。实现了小预算变体 does not decrement the caller recursion budget inside a legacy hook Goal chain:一个"仅旧版"的发送(Goal runtime 不可用,使 !goalPermit 分支持有该链),调用方预算为 2,存储中有一个活跃 goal,Stop hook 连续两次 block,必须跑满 3 次模型调用;递减预算会在第三次被拒绝。构建过程中遇到两个 harness 接缝,均按文件内既有惯例处理:Goal runtime 存活时回合会被 permit 认领并走 Goal-runtime 分支(因此用 GoalPersistenceUnavailableError 拒绝 runtime,与既有测试 keeps ordinary turns available… 相同);最后一回合的 next-speaker 检查被跳过(与该测试使用相同的 mock)。

F3(小问题)— Reviewer Test Plan 中的数字(824 个测试;32 个 tsc 错误)无法复现 → 拒绝处理。
两个数字都属于 PR 描述的漂移,不是代码问题:分支切出后 main 上的套件增长了(当前 head 为 18 个文件 / 837 个测试,仍然全绿);tsc 错误数取决于环境/分支(当前 head 两臂各 1 个相同错误,均不在改动文件中)。验证者已确认两个实质性结论都成立。没有适用的代码改动,且本模式的输出不包含改写 PR 描述。

变更内容

  • packages/core/src/core/client-goal.test.ts(+86/−1):新增两个测试,钉住本 PR 文档中声明保留的两处豁免;新增一个 afterEach 重置模块级全局 active-goal 存储,以及相应的 import。没有改动任何源码文件(client.ts 未被修改——下面的变异探针结束后已确认其处于干净状态)。

冲突说明

无 — --conflict false;未合并 origin/main

验证

实际执行的命令及结果(按顺序):

  • cd packages/core && npx vitest run src/core/client-goal.test.ts25 通过(23 个既有 + 2 个新增);连续重复 3 次,每次均 25 通过(无抖动)。
  • 变异探针 M3(临时,随后还原):在 client.ts 中移除 steer 输入门禁里的 !isGoalRuntimeTurn && → 恰好新测试 admits a runtime Goal turn to steer input at a hit session cap 失败(1 失败 / 24 通过);还原 client.ts → 恢复全绿。新测试确实钉住了 steer 输入豁免。
  • 变异探针 M4(临时,随后还原):在 client.ts 中将 hookTurnBudget = activeGoal ? boundedTurns : boundedTurns - 1 改为始终递减 → 恰好新测试 does not decrement the caller recursion budget inside a legacy hook Goal chain 失败;还原 client.ts → 恢复全绿。新测试确实钉住了旧版 hook 预算豁免。git status 确认未残留任何 client.ts 改动。
  • cd packages/core && npx vitest run src/core/client-goal.test.ts src/goals/goalHook.test.ts src/goals/goalLoop.integration.test.ts src/goals/activeGoalStore.test.ts4 个文件,73 通过
  • npm run typecheck通过
  • npm run lint(全仓库 eslint . + integration-tests)— 通过
  • npm run build通过
  • npx prettier --check packages/core/src/core/client-goal.test.ts通过

给确定性门禁的说明:两个新测试钉住的是既有的保留行为(纯覆盖补充),因此它们在"本轮之前"的分支上同样会通过,这是预期行为;其区分能力已由上面的 M3/M4 变异探针证明,与验证者自己的实测一致。

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

@qqqys

qqqys commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Closing in favour of #10259 (same two commits, merged with current main).

Both automatic review runs on this PR — run 32953769318 on fa383715f and run 33041166019 on 6457ebf7e — completed green and computed Approve, 0 Critical, 0 Suggestion, but neither could post: a stale empty PENDING review by the review bot on this PR makes every review submit fail with 422 (one pending review per user per pull request), and that pending object is bound to this PR number. A new PR is the only way to get a clean slate; nothing about the change itself is affected.

中文说明

关闭并由 #10259 取代(同样的两个 commit,已合并当前 main)。本 PR 的两轮自动评审都跑绿且判定 Approve、零发现,但均未能发出:评审机器人在本 PR 残留了一个空的 PENDING review,使每次 review submit 都 422,且该对象绑定在本 PR 号上。改动本身不受影响。

@qqqys qqqys closed this Aug 27, 2026
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