Skip to content

feat(config): let operators size or disable the Goal token budget - #10543

Merged
qqqys merged 7 commits into
QwenLM:mainfrom
qqqys:goal/configurable-token-budget
Aug 31, 2026
Merged

feat(config): let operators size or disable the Goal token budget#10543
qqqys merged 7 commits into
QwenLM:mainfrom
qqqys:goal/configurable-token-budget

Conversation

@qqqys

@qqqys qqqys commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Adds one setting, model.goalTokenBudget, and carries it into the Goal runtime so an operator can size or switch off the autonomous spend window that #9891 arms on each new Goal.

For settings.json, a positive integer from 1 through 300,000,000 is the grant, measured as the totalTokenCount summed over model calls made in the Goal's own turns. -1 means unlimited/no cap. Leaving the setting unset keeps the built-in default of 30,000,000. Zero, values above the typo-guard ceiling, other negative values, fractions, and non-numbers are rejected at startup instead of silently widening the budget. Host integrations that construct the core configuration directly retain the lenient normalization path, including the existing 0 opt-out alias.

When a Goal spends its window it receives one wind-down turn to hand off, then stops until it is resumed, which arms another window. Side queries and checkpoint verification remain outside the meter, and the Goal runtime itself is unchanged.

Why it's needed

#9891 exposed a budget grant to Goal hosts, but the production CLI never supplied an operator-configured value, so every session used the 30M default. During the end-to-end replay that surfaced #10172, bounding a run to 400k required editing a constant and rebuilding the CLI. Operators running small unattended tasks need a tighter gate, while genuinely long Goals need a deliberate way to raise or disable it; both should be a settings change with typo-safe startup validation.

Reviewer Test Plan

How to verify

  • Focused core configuration suite: 596/596 pass, covering configured/default/unlimited grants, the 300M boundary, host-facing fallback behavior, warning behavior, and propagation into a newly created Goal.
  • Focused CLI configuration, validation, schema, and main-conflict regression suites: 475/475 pass, covering positive values and -1, startup rejection for 0, negative/fractional/non-number/above-cap values, generated schema consistency, and the mainline ECS-aware timeout behavior.
  • Repository build and typecheck both pass.
  • Regenerating the settings schema produces the committed schema.
  • Live positive-budget evidence remains the same: a bundled run with model.goalTokenBudget: 400000 created a Goal with a 400,000-token window and completed within it.

Evidence (Before & After)

Journal goal_state records from the live run, snapshot.goal fields:

create           turns=0 tokensUsed=0      tokenBudget=400000 status=active
turn_finished    turns=1 tokensUsed=166503 tokenBudget=400000 status=active
verifier_accept  turns=1 tokensUsed=166503 tokenBudget=400000 status=active
complete         turns=1 tokensUsed=166503 tokenBudget=400000 status=complete

Before this PR the same run's create record would carry tokenBudget=30000000 regardless of settings.

Tested on

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

Environment (optional)

Unit tests plus one live headless run against DashScope with the setting in the operator configuration.

Risk & Scope

  • Main risk or tradeoff: -1 deliberately disables the runaway bound. Invalid operator settings now fail at startup rather than silently falling back; this is intentional for a safety budget. The default remains unchanged.
  • Not validated / out of scope: no CLI flag, no per-Goal override, no /goal budget command, and no settings-dialog entry.
  • Breaking changes / migration notes: none for released behavior. The setting is optional and additive.

Linked Issues

中文说明

这个 PR 做了什么

新增 model.goalTokenBudget 设置,并把它传入 Goal runtime,让 operator 可以调整或关闭 #9891 为每个新 Goal 武装的自主消费窗口。

settings.json 而言,1 到 300,000,000 的正整数表示预算额度,单位是 Goal 自身轮次内模型调用的 totalTokenCount 之和;-1 表示无限制/不设上限;不设置时继续使用内置默认值 30,000,000。0、超过防笔误上限的值、其他负数、小数和非数值会在启动时直接报错,不再静默扩大预算。直接构造 core 配置的 host 集成仍保留宽松归一化路径,包括既有的 0 opt-out 别名。

Goal 用完窗口后会获得一个收尾轮次用于交接,随后停止,直到 resume 时重新武装窗口。Side query 和 checkpoint 验证仍不计入预算,Goal runtime 本身未改。

为什么需要

#9891 已向 Goal host 暴露预算 grant,但生产 CLI 没有传入 operator 配置,因此所有 session 都只能使用 3000 万默认值。在发现 #10172 的端到端回放中,要把一次运行限制在 40 万必须修改常量并重编 CLI。小型无人值守任务需要更紧的闸门,真正长时间运行的 Goal 则需要明确地提高或关闭预算;两者都应通过带防笔误启动校验的设置完成。

评审验证计划

如何验证

  • Core 配置聚焦测试 596/596 通过,覆盖显式/默认/无限预算、300M 边界、host 侧宽松回退、警告行为,以及新建 Goal 的预算透传。
  • CLI 配置、校验、schema 与主线冲突回归聚焦测试 475/475 通过,覆盖正数与 -10/负数/小数/非数值/超上限值的启动拒绝、生成 schema 一致性,以及主线 ECS-aware 超时行为。
  • 仓库级 build 与 typecheck 均通过。
  • 重新生成 settings schema 与提交内容一致。
  • 正数预算的真实运行证据保持不变:使用 model.goalTokenBudget: 400000 的 bundle 运行创建了 40 万 token 窗口,并在窗口内完成。

证据(前后对比)

真实运行的 goal_state 记录见上方英文代码块。本 PR 之前,同样运行的 create 记录无论如何设置都会带 tokenBudget=30000000

已测试平台

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

环境(可选)

单元测试之外,另有一次在 operator 配置中带该设置、针对 DashScope 的真实 headless 运行。

风险与范围

  • 主要风险或权衡:-1 会明确关闭失控保护。非法 operator 配置现在会在启动时报错,而不是静默回退;对于安全预算这是刻意行为。默认值不变。
  • 未验证/范围外:无 CLI flag、无按 Goal 覆盖、无 /goal budget 命令、无设置对话框条目。
  • 破坏性变更/迁移说明:对已发布行为无破坏性变更;该设置可选且纯增量。

关联 Issue

QwenLM#9891 gave createGoalRuntime a tokenBudgetGrant option, but the only
production constructor never passed it, so every session ran on the
30M default with no way to raise, lower, or switch it off. The number
was already user-visible (get_goal reports it, a budget stop prints
it); it just was not user-settable.

`model.goalTokenBudget` sits beside the other run ceilings. A positive
integer is the grant, in the tokens the Goal meter counts. 0 runs Goals
with no budget by mapping to the runtime's non-finite opt-out, so the
created Goal simply carries no tokenBudget field and nothing non-finite
is persisted. Anything invalid falls back to the default with a debug
warning. It reaches core Config the way maxSessionTurns does and is
passed at the single createGoalRuntime call; the runtime is untouched.

Verified live: with `{"model":{"goalTokenBudget":400000}}` in the
workspace settings, the session journal's create record carries
tokenBudget=400000 and the Goal completed in one turn at 166,503.

Mutation probes: dropping the pass-through fails the 1234 and the 0
tests; dropping the 0-to-unbounded mapping fails the 0 test and the
normalizer test.
@qqqys

qqqys commented Aug 30, 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 30, 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 30, 2026

Copy link
Copy Markdown
Collaborator

⚠️ Deferred approval withheld — 1 PR CI workflow run(s) on ca71394 did not finish green; see the updated table in the Stage 2 comment. Re-run @qwen-code /triage after fixes. finalize run

⚠️ 延迟审批已搁置 —— ca71394 有 1 个 PR CI workflow 未以绿色完成,详见 Stage 2 评论中已更新的表格。修复后可重新运行 @qwen-code /triage查看 finalize 运行

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: Observed gap, not theoretical. #9891 gave createGoalRuntime a tokenBudgetGrant option, but the only production constructor (Config.getGoalRuntime()) never passes it, so every Goal runs on the hard-coded 30M default with no operator control. The #10172 replay needing a 400k bound — and requiring a constant edit + rebuild to get one — is a concrete scenario, and the number is already user-visible (get_goal summary, budget-stop lastReason), just not settable.

Direction: Aligned. This is an operator knob for an existing, already-merged runtime capability, in the same family as the sibling ceilings (maxSessionTurns, maxWallTimeSeconds, maxToolCalls). The 0 opt-out reuses the runtime's existing non-finite-grant surface rather than inventing new semantics. Claude Code's CHANGELOG has no direct counterpart for a configurable Goal budget, but the area (goal budgeting, integer knobs like "token budgets" accepting 1e6-style spellings) is active there — supporting signal only.

Size: core paths touched (packages/core/src/config/**, packages/cli/src/config/**): 61 production lines (core/config.ts +50, cli/settingsSchema.ts +10, cli/config.ts +1), 82 test lines, 4 generated/schema lines (settings.schema.json), 1 docs line. Well under any escalation threshold.

Approach: Minimal and matches the obvious path: schema entry → ConfigParameters field → normalize/validate → pass at the single production createGoalRuntime call. No CLI flag, no /settings dialog entry, no per-Goal override — all explicitly deferred, which keeps the diff focused. Nothing unrelated in the diff.

Risk: No elevated risk signals — none of the changed files match the revert-correlated high-risk paths.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题: 已观测到的缺口,不是理论问题。#9891createGoalRuntime 加了 tokenBudgetGrant 选项,但唯一的生产构造点(Config.getGoalRuntime())从未传它,所以每个 Goal 都跑在硬编码的 3000 万默认值上,operator 无法控制。#10172 回放需要 40 万上限、却只能改常量重编——这是具体场景;而且这个数字早已对用户可见(get_goal 摘要、预算停止的 lastReason),只是不可设置。

方向: 对齐。这是对已合入的 runtime 能力加一个 operator 旋钮,与兄弟上限项(maxSessionTurnsmaxWallTimeSecondsmaxToolCalls)同族。0 关闭预算复用了 runtime 已有的非有限 grant 语义,没有发明新东西。Claude Code 的 CHANGELOG 没有可配置 Goal 预算的直接对应项,但该领域(goal 预算、整数类旋钮如 "token budgets")在那边也活跃——仅作参考信号。

规模: 触及核心路径(packages/core/src/config/**packages/cli/src/config/**):61 行生产代码(core/config.ts +50、cli/settingsSchema.ts +10、cli/config.ts +1),82 行测试,4 行生成/schema(settings.schema.json),1 行文档。远低于任何升级阈值。

方案: 最小化且就是显而易见的路径:schema 条目 → ConfigParameters 字段 → 规范化/校验 → 在唯一的生产 createGoalRuntime 调用处传入。无 CLI flag、无 /settings 对话框条目、无按 Goal 覆盖——均明确推迟,diff 保持聚焦,无无关改动。

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

进入代码审查 🔍

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code review — I wrote my own proposal before reading the diff (schema entry → ConfigParameters field → normalize → pass at the runtime constructor), and this PR is exactly that shape. I then verified the load-bearing claims against the base:

  • createGoalRuntime does accept tokenBudgetGrant (goal-runtime.ts, defaulting to GOAL_DEFAULT_TOKEN_BUDGET), and Config.getGoalRuntime() really is the only production call site — everything else is tests. So the "never passed it" framing is accurate.
  • this.debugLogger is assigned early in the Config constructor (before the new invalid-value warning runs), so the warning path can't throw on ordering.
  • The 0 → Infinity → no budget mapping reuses the runtime's existing non-finite-grant path (rearmedTokenBudget arms nothing non-finite), which is already covered upstream by a tokenBudgetGrant: Number.POSITIVE_INFINITY runtime test — nothing non-finite is persisted, as claimed.
  • Normalization is sound: positive integer passes through; 0 is the opt-out; negative / fractional / NaN / '12' / Infinity all fall back to the 30M default with one debug warning. Number.isInteger keeps a settings-file Infinity from sneaking through as a grant.
  • The schema entry matches sibling conventions (requiresRestart: false, showInDialog: false, default: undefined like reasoningEffort), the committed settings.schema.json matches the schema source, and the docs row lands in the right #### model table (the hunk's "operator scopes" context line belongs to the review section above — the placement itself is correct).

The tests pin the wiring, not just the normalizer: they build a real Config, create the runtime, dispatch create, and assert on the Goal snapshot (tokenBudget: 1_234 / absent for 0), so removing the pass-through would fail them.

One nit, non-blocking but worth fixing before merge: the new this.goalTokenBudgetGrant = normalizeGoalTokenBudget(params.goalTokenBudget); line is 81 characters — one past prettier's 80-column printWidth, so npm run format would rewrap it. Fork CI doesn't run the lint/prettier job (no lint check exists on this commit), which is presumably how it slipped past the "prettier clean" claim; a maintainer preflight would catch it. (This PR carries the autofix/takeover label, so an autofix pass can pick it up.) The author's mutation-probe and live-journal results are their own report — I verified the test wiring statically rather than re-running anything.

CI evidence — the PR's own checks on the reviewed commit, fetched via API (nothing re-run here):

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

Check Conclusion
Test (ubuntu-latest, Node 22.x) 🚫 cancelled
web-shell E2E Smoke (ubuntu-latest, Node 22.x) 🚫 cancelled
Classify PR ✅ success
Dependency CVE audit ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Integration Tests (no-AK, No Sandbox) ✅ success
route ✅ success
Secret scan (TruffleHog) ✅ success

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

The main ubuntu unit/integration suite is still running; macOS/Windows matrix legs are skipped in fork CI, so Linux-only coverage is what this PR will land on. Note fork CI also runs no lint/format/typecheck leg — the prettier nit above was found by static reading, and would have surfaced there.

Sandboxed verification would settle the one claim CI can't: that the setting reaches a live session end-to-end (the journal goal_state evidence is the author's own report). @qwen-code /verify — an A/B run with model.goalTokenBudget set (e.g. 400000) would confirm the armed tokenBudget in a real session and that 0 arms none; the author has write access, so the trigger is available directly.

中文说明

代码审查 —— 我在看 diff 之前先写了自己的方案(schema 条目 → ConfigParameters 字段 → 规范化 → 在 runtime 构造处传入),本 PR 与之一致。随后对照基线验证了关键论断:

  • createGoalRuntime 确实接受 tokenBudgetGrant(默认 GOAL_DEFAULT_TOKEN_BUDGET),且 Config.getGoalRuntime() 确实是唯一生产调用点——其余全是测试,"从未传过"的说法属实。
  • this.debugLoggerConfig 构造函数中较早赋值(早于新增的非法值警告),警告路径不会因顺序问题抛错。
  • 0 → Infinity → 无预算 映射复用 runtime 已有的非有限 grant 路径(rearmedTokenBudget 不武装非有限值),上游已有 tokenBudgetGrant: Number.POSITIVE_INFINITY 的 runtime 测试覆盖——确如所述不会持久化任何非有限值。
  • 规范化逻辑严密:正整数直通;0 为关闭项;负数/小数/NaN/'12'/Infinity 均回退 3000 万默认并打一行 debug 警告。Number.isInteger 挡住设置文件里的 Infinity
  • schema 条目与兄弟项约定一致,已提交的 settings.schema.json 与 schema 源一致,文档行落在正确的 #### model 表中(hunk 上下文里的 "operator scopes" 属于上方的 review 段——位置本身正确)。

测试锁住的是接线而不仅是规范化函数:它们构造真实 Config、创建 runtime、dispatch create 并断言 Goal 快照(tokenBudget: 1_234 / 0 时不存在),去掉透传就会失败。

一个非阻塞的小问题,合并前值得修:新增的 this.goalTokenBudgetGrant = normalizeGoalTokenBudget(params.goalTokenBudget); 一行有 81 字符,超出 prettier 的 80 列 printWidthnpm run format 会重排它。fork CI 不跑 lint/prettier(该提交上没有 lint 检查),这大概是"prettier clean"说法漏网的原因;维护者 preflight 会抓到。(本 PR 带 autofix/takeover 标签,autofix 可以顺手修掉。)作者的变异检验与真实运行日志是其自述——我以静态方式验证了测试接线,未重跑任何东西。

CI 证据 —— 来自该提交自身的 CI 检查(经 API 抓取,此处未重跑):表格见英文部分(表内为机器可读区域,由 finalize 工作流在 CI 落定后更新)。

主要的 ubuntu 单元/集成套件仍在运行;macOS/Windows 矩阵在 fork CI 中被跳过,本 PR 将以 Linux 单平台覆盖合入。另注意 fork CI 也不跑 lint/format/typecheck——上面的 prettier 问题是静态阅读发现的,本来应由那里暴露。

沙盒验证可以补足 CI 无法覆盖的一个论断:设置项真正到达真实 session(journal goal_state 证据是作者自述)。@qwen-code /verify —— 一次设置 model.goalTokenBudget(如 400000)的 A/B 运行,可以确认真实 session 中武装的 tokenBudget、以及 0 时不武装;作者有写权限,可以直接触发。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean, minimal wiring of an existing runtime option with tests that pin it; one auto-fixable prettier nit, and the main CI suite is still running.

Honest read: this is what a small config PR should look like. The option already existed in the runtime (tokenBudgetGrant from #9891) but was never passed by the only production constructor, so every session silently ran on the 30M default — the PR closes exactly that gap and nothing more. The semantics reuse the runtime's existing surfaces (non-finite grant for opt-out, no Infinity ever persisted), the invalid-value handling fails soft with a warning, and the tests exercise the real Config → runtime path rather than just the normalizer. My independent proposal matched the diff one for one, which is as close as this gets.

Two things keep it at 4 rather than 5. The 81-character line in core/config.ts trips prettier's 80-column width — fork CI has no lint leg so it wasn't caught, but a maintainer preflight will. Trivial, and the autofix/takeover label is on the PR. And the end-to-end journal evidence is the author's own report; I verified the wiring statically and named @qwen-code /verify as the lane that would settle the live-session claim. Neither blocks.

On volume: the author has a batch of open PRs this week; I evaluated this one on its own merits and it stands on them — focused, evidenced, no scope creep.

Approval deferred until CI lands green on ca71394ea1c451b7f75f1099656f4ac4939377bb — the ubuntu unit/integration suite is still running, and approving now would attest to a result that doesn't exist yet. If it lands green, the approval follows automatically; if anything lands red or the head moves, this is withheld.

中文说明

置信度:4/5 —— 干净、最小化地接入了 runtime 已有的选项,测试锁住了接线;一个可自动修复的 prettier 小问题,主 CI 套件仍在运行。

坦率地说:这是一个小型配置 PR 该有的样子。该选项在 runtime 中早已存在(#9891tokenBudgetGrant),但唯一的生产构造点从未传入,于是每个 session 都默默跑在 3000 万默认值上——本 PR 恰好补上这个缺口,不多不少。语义复用 runtime 已有表面(非有限 grant 作为关闭项,绝不持久化 Infinity),非法值软失败并打警告,测试走的是真实 Config → runtime 路径而非仅测规范化函数。我独立写出的方案与 diff 完全一致。

两点让它停在 4 而不是 5。core/config.ts 中那行 81 字符的代码超出了 prettier 的 80 列——fork CI 没有 lint 环节所以没被抓到,但维护者 preflight 会抓到。问题很小,且 PR 上已有 autofix/takeover 标签。另外,端到端的日志证据是作者自述;我以静态方式验证了接线,并指出 @qwen-code /verify 是补足真实 session 论断的途径。两者均不阻塞。

关于数量:作者本周有一批打开的 PR;我按各自功过评估,这一个站得住——聚焦、有证据、无范围蔓延。

审批推迟到 CI 在被审提交上全绿之后——ubuntu 单元/集成套件仍在运行,现在批准等于为一个尚不存在的结果背书。若全绿,审批会自动跟上;若有红项或 head 移动,则保留不批。

Qwen Code · qwen3.8-max

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

Reviewed. Suggestions are inline.

中文说明

已审查。 建议见行内评论。

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

Comment thread packages/cli/src/config/settingsSchema.ts Outdated
Comment thread packages/core/src/config/config.ts
Comment thread packages/core/src/config/config.ts Outdated
Comment thread packages/cli/src/config/config.ts Outdated
Comment thread packages/core/src/config/config.ts
@qwen-code-dev-bot

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

Copy link
Copy Markdown
Collaborator

⚠️ AutoFix round 7 ended without publishing a reportview run.

中文说明

⚠️ AutoFix 第 7 轮结束但未发布报告 —— 查看运行

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix hit a verification-gate error before reaching a verdict (attempt 1/100) — it will retry on the next scan.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:

Autofix review round — PR #10543 (round 1)

Commit: fix(config): make goalTokenBudget fallback visible and accept -1 opt-out (#10543) (6 files, +113/−30)

Feedback disposition

R1-1 — "ignored with a warning" promise is invisible by default (anchors: settingsSchema.ts:1605, core/config.ts:2594) → Fixed, visible-channel route.
The claim was verified against the code: writeLog in debugLogger.ts returns early unless QWEN_DEBUG_LOG_FILE is set, so the only warning was invisible on every default run. Fix: the constructor warning now goes through console.warn, following the in-file precedent resolveCronRecurringMaxAgeDays, which uses console.warn for this exact trap ("debug file logging is usually off", the misconfiguration would otherwise surface silently). The constructor channel was chosen over the getSettingsWarnings startup path because model.goalTokenBudget is an operator-scopes-only setting (User / System / SystemDefaults) while getSettingsWarnings scans User + Workspace files — the Config constructor is the single normalization point that covers every settings source. The docs/schema promise "ignored with a warning" is now true on all three surfaces, so no rewording was needed.

R1-2 — the warning branch has no test (core/config.ts:2591) → Fixed.
Added construction-time tests next to the existing goalTokenBudget tests, following the console-warn spy precedent at config.test.ts:1200: goalTokenBudget: -5 warns e

中文说明

🤖 AutoFix 在得出结论之前遇到验证门错误(第 1/100 次尝试)—— 将在下次扫描时重试。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/33298114936


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

qqqys and others added 2 commits August 30, 2026 13:57
QwenLM#10543)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…ion (QwenLM#10543)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Review round summary — PR #10543 (same-run verification repair)

This round is the same-run verification repair for the deterministic rejection of commit 0e7aff632b: tests failed in packages/cli — two tests in src/serve/server-default-bridge-wiring.test.ts timed out at exactly 15000ms during the gate's full --changed origin/main run (702 files / 22,347 tests). The rejected commit is preserved unchanged; this round adds one follow-up commit, 732085dc5b.

Diagnosis (evidence, not guess)

  • Mechanism. Every test in that file runs vi.resetModules() in afterEach, then re-imports the full ./server.js module graph with a vi.importActual of the bridge module on top. In isolation the whole file passes in ~25s (worst single test 3.9s), but the gate ran the 702-file changed set with the default worker pool on a shared 64-core host — the heavy cold import exceeded the package's 15s testTimeout (packages/cli/vitest.config.ts already documents that this ceiling exists because of CI contention).
  • Reproduced the same failure class locally. A full-suite run on this same machine reproduced the timeout in the same file with the identical signature — but on a different test (wires the internally-created bridge lifecycle into the workspace registry, timed out at exactly 15000ms; the file's suite time stretched to 64s while its isolated runs stay ~25s). The victim is random per run; the gate hit tests 2 and 4, the local repro hit test 1. Random victim + identical signature = load spike, not a deterministic regression.
  • Not caused by this PR. The PR diff touches only goalTokenBudget config wiring/normalizer/docs/schema; it adds one property line to loadCliConfig and no module-level side effects, nothing that can slow a module import. The same gate run passed 22,255 other tests, including the other five tests of this very file.
  • House precedent. 8e0033d64d ("reduce ENOSPC and load-sensitive test flakes") documents exactly this failure mode — "the contention is what blows the 15s ceilings those configs already complain about: tests that pass locally in milliseconds time out" — and packages/cli/vitest.config.ts raises testTimeout to 15s for the same reason.
  • Env-parity note (for the record). An early local repro showed additional failures (settings.test.ts ×12, etc.). Those were artifacts of the agent session environment (QWEN_HOME exported by the reviewing Qwen Code session leaked into settings-loader tests; HOME is not writable by the test process). They disappear once the env is aligned with the gate's shape (no SANDBOX marker, no QWEN_HOME, writable HOME) and they never appeared in the gate's own run of this commit.

Fix applied (deflake, assertion-preserving)

Per the repo deflake skill's allowed fix #1 — raise the timeout ceiling, change nothing else — all seven tests in server-default-bridge-wiring.test.ts now carry an explicit 60_000 per-test timeout (two raised from 15_000, five added; the bare-literal form keeps prettier's compact layout, only the it.each block is expanded as prettier mandates). All tests share the identical load-sensitive resetModules + re-import pattern, so ceiling only the two observed victims would leave the file flaky. No assertion, input, or test body was changed — only the ceiling moved. The file passes 3/3 repeated isolated runs, and the gate-equivalent full packages/cli suite (exact --changed origin/main set, 702 files) passes with the fix.

Prior-round findings (re-verified still resolved)

All five inline findings remain resolved in code by the preserved commit 0e7aff632b; this round re-verified each against the live tree and re-ran the focused tests:

  • R1-1 (rc:3888683779, rc:3888683781) — schema description, docs row, and regenerated settings.schema.json say "fall back to the default (recorded in the debug log file)" (reviewer's route b); the debug warning itself is kept and test-pinned.
  • R1-2 (rc:3888683782) — the warning branch is pinned by two core tests (fires for -5 with value+default in the message; silent for undefined, 0, 1_234, -1, with a live-channel control).
  • R1-3 (rc:3888683786) — loadCliConfig wiring is pinned by the describe('model.goalTokenBudget') block in the CLI config tests (1_234 set → grant 1_234; unset → GOAL_DEFAULT_TOKEN_BUDGET).
  • R1-4 (rc:3888683789) — -1 is accepted as an opt-out alias for 0 (normalizeGoalTokenBudget maps both to Infinity, isValidGoalTokenBudget accepts -1), on the Config side, with the 0 semantics preserved.

Mutation probes

This round's commit adds no new guard, branch, or behavior — only test timeout ceilings (a deflake), so there is no new guard to witness. The witnesses for the change are the observed failure signatures themselves (two independent runs, three timed-out tests, all at exactly 15000ms on the same import-heavy pattern), and the ceiling change cannot mask a correctness regression: any assertion failure still fails at any timeout. The prior round's five mutation probes (warn-block deletion, guard removal, -1 mapping removal, validator revert, wiring-line deletion) remain valid on the preserved commit and their focused tests were re-run green this round.

Other notes

  • The Failed checks entries in the feedback (Test, web-shell E2E Smoke) are CANCELLED, not red — no code-level action; the workflow's independent CI remains the final gate.
  • Footprint: this round touches packages/cli/src/serve/ which the PR had not touched before. The expansion is required by the same-run verification repair itself (the rejection lives in that file, and no change inside the PR's original footprint can affect a 15s import timeout); the change is confined to that one test file and raises ceilings only.

Verification

Commands actually run this round (agent-session env scrubbed to gate parity: no SANDBOX, no QWEN_HOME, writable HOME):

  • npx vitest run src/serve/server-default-bridge-wiring.test.ts (packages/cli) — 7 passed, repeated 3/3 before and after formatting
  • npx vitest run --changed origin/main --passWithNoTests (packages/cli, gate's exact set) — 702 files passed, 22263 passed | 84 skipped (22347)
  • npx vitest run src/config/config.test.ts -t goalTokenBudget (packages/core) — 9 passed (prior-round fixes intact)
  • npx vitest run src/config/config.test.ts -t goalTokenBudget (packages/cli) — 2 passed (prior-round wiring tests intact)
  • npm run build — passed (exit 0)
  • npm run typecheck — passed (exit 0)
  • npm run lint — passed (exit 0)
  • npx eslint packages/cli/src/serve/server-default-bridge-wiring.test.ts — passed
  • npx prettier --check (changed file) — passed
  • Full-suite diagnostic runs (evidence-gathering, before the fix): one full vitest run reproducing the same-file 15000ms timeout on test 1; isolated settings.test.ts runs proving the 12 local failures were session-env artifacts (174/174 pass in gate-parity env)
中文说明

审查轮次总结 — PR #10543(同轮验证修复)

本轮是针对提交 0e7aff632b 被确定性验证关卡拒绝的同轮验证修复:拒绝原因为 tests failed in packages/cli —— 关卡执行完整的 --changed origin/main 运行(702 个文件 / 22,347 个测试)时,src/serve/server-default-bridge-wiring.test.ts 中有两个测试恰好在 15000ms 处超时。被拒提交原样保留,本轮新增一个后续提交 732085dc5b

诊断(基于证据,而非猜测)

  • 机制。 该文件中每个测试都在 afterEach 里执行 vi.resetModules(),然后重新 import 整个 ./server.js 模块图,外加对 bridge 模块的 vi.importActual。单独运行时整个文件约 25 秒通过(最慢的单个测试 3.9 秒),但关卡在共享的 64 核机器上以默认 worker 池运行 702 个文件的变更集——沉重的冷导入超过了包级 15 秒 testTimeout(packages/cli/vitest.config.ts 中已有注释说明该上限正是为 CI 资源竞争而设)。
  • 本地复现了同一类失败。 在同一台机器上跑完整套件,复现了同一个文件中签名完全相同的超时——但命中的是另一个测试(wires the internally-created bridge lifecycle into the workspace registry,恰好在 15000ms 超时;该文件套件耗时被拉长到 64 秒,而单独运行只有约 25 秒)。每次运行的受害者是随机的:关卡命中第 2、4 个测试,本地复现命中第 1 个。受害者随机 + 签名相同 = 负载尖峰,而非确定性回归。
  • 与本 PR 无关。 PR 的 diff 只涉及 goalTokenBudget 的配置接线/规范化函数/文档/schema;仅给 loadCliConfig 增加了一行属性,没有任何模块级副作用,不可能拖慢模块导入。同一次关卡运行中其余 22,255 个测试全部通过,包括本文件中另外五个测试。
  • 仓库先例。 8e0033d64d("reduce ENOSPC and load-sensitive test flakes")明确记录过同一失败模式——"竞争正是击穿这些配置已在抱怨的 15 秒上限的原因:本地毫秒级通过的测试会超时";packages/cli/vitest.config.ts 也正是为此把 testTimeout 提到了 15 秒。
  • 环境一致性说明(留档)。 早期一次本地复现还出现了额外失败(settings.test.ts ×12 等)。那些是代理会话环境的假象(审查用的 Qwen Code 会话导出的 QWEN_HOME 泄漏进了 settings 加载测试;HOME 对测试进程不可写)。把环境对齐到关卡形态(去掉 SANDBOX 标记、去掉 QWEN_HOME、可写的 HOME)后它们全部消失,而且关卡自己对本提交的运行中也从未出现过这些失败。

应用的修复(去抖动,保持断言不变)

按仓库 deflake 技能允许的修复方式 #1——只抬高超时上限,其他一概不动——server-default-bridge-wiring.test.ts 中全部七个测试现在都带有显式的 60_000 单测超时(两个从 15_000 抬高,五个新增;裸数字字面量保持了 prettier 的紧凑排版,只有 it.each 块按 prettier 要求被展开)。七个测试共享完全相同的负载敏感模式(resetModules + 重新导入),只给被观测到的两个受害者抬上限会让文件继续抖动。断言、输入、测试体一概未变——只有上限移动了。文件在 3/3 次重复单独运行中通过,修复后的关卡等价完整 packages/cli 套件(--changed origin/main 精确集合,702 个文件)也通过。

上一轮的发现(重新验证仍已解决)

全部五条行内发现仍由保留的提交 0e7aff632b 在代码层面解决;本轮对照当前代码树逐条重新验证,并重新运行了聚焦测试:

  • R1-1(rc:3888683779、rc:3888683781)—— schema 描述、文档行与重新生成的 settings.schema.json 均写明"回退到默认值(记录在 debug 日志文件中)"(审查者的路线 b);debug 警告本身保留并由测试钉住。
  • R1-2(rc:3888683782)—— 警告分支由两个 core 测试钉住(-5 触发且消息含非法值与默认值;undefined01_234-1 保持沉默,并带一个通道可用性对照)。
  • R1-3(rc:3888683786)—— loadCliConfig 接线由 CLI 配置测试中的 describe('model.goalTokenBudget') 块钉住(设置 1_234 → grant 1_234;未设置 → GOAL_DEFAULT_TOKEN_BUDGET)。
  • R1-4(rc:3888683789)—— -1 被接受为与 0 等价的关闭预算别名(normalizeGoalTokenBudget 把两者都映射为 Infinity,isValidGoalTokenBudget 接受 -1),映射发生在 Config 一侧,0 的语义保持不变。

变异探针

本轮提交没有新增任何守卫、分支或行为——只有测试超时上限(去抖动),因此没有需要见证的新守卫。本次修改的见证是观测到的失败签名本身(两次独立运行、三个超时的测试,全部恰好在 15000ms、同一种重导入模式上),且抬高上限不会掩盖正确性回归:任何断言失败在任何超时下都会失败。上一轮的五项变异探针(删除警告块、去掉守卫、移除 -1 映射、还原校验器、删除接线行)在保留提交上依然有效,其聚焦测试本轮重新运行均为绿。

其他说明

  • 反馈中列出的 Failed checks(Testweb-shell E2E Smoke)状态为 CANCELLED(已取消) 而非红色——无需代码层面处理;工作流的独立 CI 仍是最终关卡。
  • 足迹:本轮触及了此前 PR 从未触碰过的 packages/cli/src/serve/。这一扩展是同轮验证修复本身所要求的(拒绝就出在该文件,而 PR 原始足迹内的任何改动都无法影响 15 秒的导入超时);改动仅限于这一个测试文件,且只抬高上限。

验证

本轮实际执行的命令(代理会话环境已对齐关卡形态:去掉 SANDBOX、去掉 QWEN_HOME、可写的 HOME):

  • npx vitest run src/serve/server-default-bridge-wiring.test.ts(packages/cli)—— 7 通过,格式化前后各重复 3/3 次
  • npx vitest run --changed origin/main --passWithNoTests(packages/cli,关卡精确集合)—— 702 个文件通过,22263 通过 | 84 跳过 (22347)
  • npx vitest run src/config/config.test.ts -t goalTokenBudget(packages/core)—— 9 通过(上一轮修复保持完好)
  • npx vitest run src/config/config.test.ts -t goalTokenBudget(packages/cli)—— 2 通过(上一轮接线测试保持完好)
  • npm run build —— 通过(exit 0)
  • npm run typecheck —— 通过(exit 0)
  • npm run lint —— 通过(exit 0)
  • npx eslint packages/cli/src/serve/server-default-bridge-wiring.test.ts —— 通过
  • npx prettier --check(改动文件)—— 通过
  • 完整套件诊断运行(取证用,修复前):一次完整 vitest run 在同一文件第 1 个测试上复现了同样的 15000ms 超时;settings.test.ts 的单独运行证明本地那 12 个失败是会话环境假象(关卡等价环境下 174/174 通过)

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

🧵 Resolved all 5 selected review thread(s). · 已关闭全部选中的 5 条评审线程。

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.

Reviewed. Suggestions are inline.

中文说明

已审查。 建议见行内评论。

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

Comment thread packages/core/src/config/config.ts
@wenshao

wenshao commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Local end-to-end verification — recommend merge after a rebase

I built this branch (732085dc5b) and drove the real bundled CLI against a local OpenAI-compatible mock whose usage.total_tokens I control per call. That is the piece that makes this PR testable for real: the Goal meter sums totalTokenCount over the Goal's own turns, so a mock that can bill 3,000 tokens a turn puts a genuine budget stop three turns away instead of thirty million.

The feature does what the description says. Every load-bearing claim reproduced end to end, including the ones the PR could only assert. The findings below are consistency and safety-of-defaults issues around the edges, not defects in the mechanism.

Environment

  • Worktree at PR head 732085dc5b, npm ci + npm run build:packages + npm run bundle; runs use dist/cli.js, not vitest.
  • Isolated HOME, workspace, and a Goal-protocol-aware mock model on 127.0.0.1:4543 (streaming SSE for Goal turns, non-streaming JSON for the checkpoint/goal verifier side queries).
  • A/B baseline is the same tree with exactly one line removed — tokenBudgetGrant: this.goalTokenBudgetGrant at the createGoalRuntime call — rebuilt and bundled separately.

What reproduced

1. The setting reaches a real session, and it is this PR's line that carries it.

value matrix

workspace .qwen/settings.json armed budget Goal turns how the Goal stopped
(unset) 30000000 103 unrelated checkpoint guard
goalTokenBudget: 5000 5000 3 token budget (5,000 tokens)
goalTokenBudget: 1 1 2 token budget (1 tokens)
goalTokenBudget: 0 (none) 103 unrelated checkpoint guard
goalTokenBudget: -1 (none) 103 unrelated checkpoint guard
goalTokenBudget: -5 30000000 103 unrelated checkpoint guard
goalTokenBudget: 1.5 30000000 103 unrelated checkpoint guard
goalTokenBudget: "5000" 30000000 103 unrelated checkpoint guard
goalTokenBudget: 5000 on the A/B baseline 30000000 103 unrelated checkpoint guard

The last row is the proof of necessity: with only the pass-through removed, the identical settings file is ignored and the same task runs 103 model calls instead of 3.

2. A configured window really stops a Goal, and the operator sees the configured number. Not just a field in the journal — the wind-down turn is granted, then the Goal settles usage_limited, and both the footer pill and /goal carry the reason:

TUI usage limited

! Goal usage limited · 3 turns · 214ms
  Goal: Draft a short release note and keep refining it
  Reason: The Goal spent its autonomous token budget (5,000 tokens). Resume the Goal to
  authorize another budget window, or clear it.

3. /goal resume arms one more window of the configured size, not the default. tokensUsed=9000, tokenBudget=5000 → resume → tokenBudget=14000, and the second stop quotes 14,000.

resume rearms

4. 0 and -1 genuinely run unbounded. The created Goal carries no tokenBudget field at all — nothing non-finite is persisted, as the PR claims — and the run only ends on an unrelated guard after 309,000 metered tokens.

5. The metering scope in the description is accurate. In a run where my mock billed 2,709,000 tokens total (103 Goal turns at 3,000 + 24 checkpoint side queries at 100,000), the Goal meter recorded exactly 309,000. Side queries and checkpoint verification are not metered, as documented.

6. Both operator scopes work, with the usual precedence.

settings scopes

7. Static and test gates. packages/core src/config/config.test.ts 595 pass; packages/cli src/config/config.test.ts 361 pass; runBudget.test.ts + settingsSchema.test.ts 90 pass; the deflaked server-default-bridge-wiring.test.ts 7 pass. npm run generate:settings-schema reproduces the committed settings.schema.json byte for byte; tsc --noEmit on core, prettier and eslint on every changed file are clean.

8. The new tests are not vacuous. Five mutants, all caught:

mutant result
drop tokenBudgetGrant at the createGoalRuntime call 3 core tests fail
drop the -1 alias in normalizeGoalTokenBudget 2 core tests fail
drop the -1 acceptance in isValidGoalTokenBudget 2 core tests fail
drop goalTokenBudget: in loadCliConfig 1 cli test fails
map 0 to the default instead of unbounded 2 core tests fail

Findings

F1 · Important — an invalid value falls back silently, and it fails in the unsafe direction.

This is the one I would like addressed before merge. Every sibling run budget rejects a typo at startup; this one does not:

invalid value loudness

workspace setting exit what the operator sees
model.maxSessionTurns: 1.5 52 Invalid maxSessionTurns: must be an integer, got 1.5
model.maxWallTimeSeconds: 0 1 settings.json: model.maxWallTimeSeconds must be > 0 (or -1 for unlimited)
model.maxToolCalls: 1.5 1 settings.json: maxToolCalls must be an integer (or -1 for unlimited)
model.goalTokenBudget: 1.5 0 nothing — starts normally, silently arms 30,000,000
model.goalTokenBudget: -5 0 nothing — starts normally, silently arms 30,000,000
model.goalTokenBudget: "5000" 0 nothing — starts normally, silently arms 30,000,000

resolveMaxWallTimeSeconds in packages/cli/src/config/config.ts states the convention this PR is joining, in its own words: "a typo in a CI guardrail should fail loud at startup, not silently disable the budget." Here the typo does not disable the budget, it enlarges it — someone who meant 1_500_000 and wrote 1500000.5, or whose config template interpolated a string, gets 30,000,000 instead, twenty times the ceiling they asked for, on the setting whose entire job is bounding autonomous spend. The debug-log line is real (I confirmed it lands in ~/.qwen/debug/<session>.txt under QWEN_DEBUG_LOG_FILE=1), but nobody who did not already suspect a problem will look there.

Suggested fix, symmetric with the neighbours: a resolveGoalTokenBudget(settings) in packages/cli/src/config/config.ts that throws settings.json: model.goalTokenBudget must be a non-negative integer or -1 (no budget); got …, leaving core's normalizer as the host-facing lenient path.

F2 · Important — 0 means "unlimited" here but "none allowed" for maxToolCalls, and the wording does not disambiguate.

Both the docs row and the schema description say 0 "runs Goals with no budget", which reads equally well as "zero tokens allowed". model.maxToolCalls: 0 in the very next row means exactly that — no calls allowed — and model.maxWallTimeSeconds rejects 0 outright. So an operator reaching for the strictest setting can write 0 intending to forbid autonomous Goal spend and get an unbounded Goal instead. The repo already treats this as a known hazard class: model.sessionTokenLimit's own row spends a clause warning about it.

Commit 2 added -1 as the sibling-consistent opt-out, which makes 0 redundant. I would either drop 0 (reject it like maxWallTimeSeconds does) or, at minimum, reword both descriptions to say "no cap / unlimited" rather than "no budget".

F3 · Important (description only) — the PR body no longer matches the diff.

Commit 2 (accept -1 opt-out) landed after the description was written, and the description still says:

0 runs Goals with no budget … Negative, fractional, or non-numeric values are ignored with a one-line debug warning and fall back to the default

-1 is now an accepted opt-out, not an ignored negative. The Reviewer Test Plan also says "592 pass (7 new)"; the file is at 595 now. Both the English and the 中文 block need the update — a reviewer approving from the description today would be approving semantics the diff no longer has.

F4 · Minor — no upper bound, unlike the siblings. maxToolCalls is capped at 1,000,000 "to catch typos" and maxWallTimeSeconds rejects anything above ~24 days. goalTokenBudget accepts any non-negative integer, so 300000000000 sails through. Cheap to add alongside F1.

F5 · Minor (merge mechanics) — the conflict is entirely in commit 3, which main has already superseded, and it is currently suppressing all CI.

The only conflicting file is packages/cli/src/serve/server-default-bridge-wiring.test.ts, from commit 3 (test(cli): deflake serve bridge wiring tests…). #10552 fixed the same flake on main the opposite way — it removed those per-test timeouts and made the package-wide ceiling ECS-aware instead (testTimeout: RUNNER_NAME startsWith 'ecs-qwen-' ? 60_000 : 15_000). Keeping commit 3's hard-coded 60_000 would also raise the ceiling on local and non-ECS runs, where main deliberately keeps 15s. Recommend dropping commit 3 on the rebase and taking main's version; the feature itself rebases cleanly.

Worth flagging while you are there: because the PR is in a conflicting state, GitHub never produced a merge ref, so no pull_request workflow has run on this branch at all. Querying the runs for 732085dc5b returns four pull_request_target bot workflows and nothing else — the green checks on this PR are the label/assign/review lane, not Qwen Code CI. There is no lint, typecheck, or unit-test signal from CI here, which is part of why I ran the gates locally above.

F6 · Nit — goalTokenBudgetReason renders "(1 tokens)". Pre-existing in #9891, but this PR is what makes single-digit budgets settable, so it is newly reachable.


Recommendation

Merge after: rebase onto main dropping commit 3, and refresh the description (F3). F1 and F2 are small, both live in packages/cli/src/config/config.ts and two description strings, and both concern how a spend ceiling fails — I would rather they land with the feature than as follow-ups, but neither is a correctness defect and I would not block on them if you would rather ship and file them.

Tested on Linux. macOS and Windows unverified here.

中文版报告

本地端到端验证 —— 建议 rebase 后合并

我在本地构建了本分支(732085dc5b)的真实产物,并用打包后的真实 CLI 对接一个本地 OpenAI 兼容 mock,由我逐次控制 usage.total_tokens。这一点是本 PR 能被真实验证的关键:Goal 计量表累加的是 Goal 自身轮次里每次模型调用的 totalTokenCount,因此一个每轮计 3,000 token 的 mock,能把真实的预算停止从三千万拉到三个回合之内。

功能与描述一致。 所有关键论断都端到端复现了,包括 PR 里只能声称、无法在单测中证明的那些。下面的问题都是边缘处的一致性与默认值安全性问题,不是机制本身的缺陷。

环境

  • worktree 停在 PR head 732085dc5b,执行 npm ci + npm run build:packages + npm run bundle;所有验证跑的是 dist/cli.js,不是 vitest。
  • 隔离的 HOME 与工作区,Goal 协议感知的 mock 模型监听 127.0.0.1:4543(Goal 轮次走流式 SSE,checkpoint / goal verifier 的 side query 走非流式 JSON)。
  • A/B 基线是同一棵树只删掉一行——createGoalRuntime 调用处的 tokenBudgetGrant: this.goalTokenBudgetGrant——单独重新构建打包。

复现结果

1. 设置真的进入了真实会话,且正是本 PR 这一行在起作用。

工作区 .qwen/settings.json 武装的预算 Goal 轮次 Goal 如何停止
(不设置) 30000000 103 无关的 checkpoint 保护
goalTokenBudget: 5000 5000 3 token 预算(5,000 tokens)
goalTokenBudget: 1 1 2 token 预算(1 tokens)
goalTokenBudget: 0 (无) 103 无关的 checkpoint 保护
goalTokenBudget: -1 (无) 103 无关的 checkpoint 保护
goalTokenBudget: -5 30000000 103 无关的 checkpoint 保护
goalTokenBudget: 1.5 30000000 103 无关的 checkpoint 保护
goalTokenBudget: "5000" 30000000 103 无关的 checkpoint 保护
goalTokenBudget: 5000A/B 基线 30000000 103 无关的 checkpoint 保护

最后一行是必要性证明:只删掉那一行透传,同样的设置文件就被完全忽略,同一个任务从 3 次模型调用变成 103 次。

2. 配置的窗口真的会停住 Goal,而且 operator 看到的就是配置的数字。 不只是日志里的一个字段——wind-down 回合被授予,随后 Goal 落到 usage_limited,footer 徽标与 /goal 都带出原因:

! Goal usage limited · 3 turns · 214ms
  Goal: Draft a short release note and keep refining it
  Reason: The Goal spent its autonomous token budget (5,000 tokens). Resume the Goal to
  authorize another budget window, or clear it.

3. /goal resume 重新武装的是配置的窗口,不是默认值。 tokensUsed=9000, tokenBudget=5000 → resume → tokenBudget=14000,第二次停止时报的是 14,000。

4. 0-1 确实无预算运行。 创建出的 Goal 根本不带 tokenBudget 字段——正如 PR 所说,不会持久化任何非有限值——整轮运行只在计量 309,000 token 之后被一个无关的保护停住。

5. 描述里的计量口径是准确的。 在一次 mock 总共计费 2,709,000 token 的运行中(103 个 Goal 轮次各 3,000,加 24 次 checkpoint side query 各 100,000),Goal 计量表只记录了 309,000。side query 与 checkpoint 验证确实不计入。

6. 两个 operator scope 都生效,优先级正常(workspace 覆盖 user)。

7. 静态检查与测试。 core src/config/config.test.ts 595 通过;cli src/config/config.test.ts 361 通过;runBudget.test.ts + settingsSchema.test.ts 90 通过;被 deflake 的 server-default-bridge-wiring.test.ts 7 通过。npm run generate:settings-schema 逐字节复现已提交的 settings.schema.json;core 的 tsc --noEmit、所有改动文件的 prettier 与 eslint 均干净。

8. 新增测试非空转。 五个变异体全部被捕获:删掉 createGoalRuntime 处的透传(core 挂 3 个)、删掉 normalizeGoalTokenBudget-1 别名(挂 2 个)、删掉 isValidGoalTokenBudget-1 的接受(挂 2 个)、删掉 loadCliConfig 里的 goalTokenBudget:(cli 挂 1 个)、把 0 映射成默认值而非无界(挂 2 个)。

问题

F1 · Important —— 无效值静默回退,而且是往不安全的方向失败。

这是我希望合并前处理的一条。同族的每个运行预算设置都会在启动时拒绝笔误,只有这个不会:

工作区设置 退出码 operator 看到什么
model.maxSessionTurns: 1.5 52 Invalid maxSessionTurns: must be an integer, got 1.5
model.maxWallTimeSeconds: 0 1 settings.json: model.maxWallTimeSeconds must be > 0 (or -1 for unlimited)
model.maxToolCalls: 1.5 1 settings.json: maxToolCalls must be an integer (or -1 for unlimited)
model.goalTokenBudget: 1.5 0 什么都没有 —— 正常启动,静默武装 30,000,000
model.goalTokenBudget: -5 0 什么都没有 —— 正常启动,静默武装 30,000,000
model.goalTokenBudget: "5000" 0 什么都没有 —— 正常启动,静默武装 30,000,000

packages/cli/src/config/config.tsresolveMaxWallTimeSeconds 的注释原话就写着本 PR 要加入的这条约定:"a typo in a CI guardrail should fail loud at startup, not silently disable the budget." 而这里笔误不是让预算失效,是让它变大——本想写 1_500_000 却写成 1500000.5,或者配置模板插值出了字符串,拿到的是 30,000,000,是本人所要上限的二十倍,而这个设置的全部职责就是给自主消费封顶。debug 日志那一行确实存在(我确认它在 QWEN_DEBUG_LOG_FILE=1 下会落到 ~/.qwen/debug/<session>.txt),但没有事先怀疑出问题的人不会去看那里。

建议的修法与邻居对称:在 packages/cli/src/config/config.ts 里加一个 resolveGoalTokenBudget(settings) 抛出 settings.json: model.goalTokenBudget must be a non-negative integer or -1 (no budget); got …,core 里那个宽松的 normalizer 保留给 host 侧。

F2 · Important —— 这里 0 表示"无上限",而 maxToolCalls0 表示"一次都不许",措辞没有消歧。

文档行与 schema 描述都写 0 "runs Goals with no budget",这句话同样可以读成"零 token 可用"。紧邻的 model.maxToolCalls: 0 恰恰就是那个意思,而 model.maxWallTimeSeconds 干脆拒绝 0。于是一个想用最严设置的 operator,写下 0 本意是禁止 Goal 自主消费,拿到的却是无界的 Goal。这个坑仓库自己已经认过:model.sessionTokenLimit 的文档行专门花了一个从句提醒同类混淆。

commit 2 加入的 -1 已经是与同族一致的 opt-out,0 因而是冗余的。我倾向于要么去掉 0(像 maxWallTimeSeconds 一样拒绝),要么至少把两处描述改成"no cap / unlimited"而不是"no budget"。

F3 · Important(仅描述)—— PR 正文与 diff 已经不一致。

commit 2(accept -1 opt-out)在描述写完之后才落,而描述仍写着"0 runs Goals with no budget … Negative, fractional, or non-numeric values are ignored"。-1 现在是被接受的 opt-out,不是被忽略的负数。Reviewer Test Plan 里的 "592 pass (7 new)" 现在也是 595。英文段与中文段都需要更新——今天按描述来批准的 reviewer,批准的是 diff 已经不具备的语义。

F4 · Minor —— 没有上界,和同族不一致。 maxToolCalls 封顶 1,000,000"以捕捉笔误",maxWallTimeSeconds 拒绝超过约 24 天的值。goalTokenBudget 接受任意非负整数,300000000000 也能通过。可以和 F1 一起顺手加上。

F5 · Minor(合并机制)—— 冲突全部落在 commit 3,而 main 已经用相反的方式解决了同一个问题;并且这个冲突正在压制所有 CI。

唯一冲突的文件是 packages/cli/src/serve/server-default-bridge-wiring.test.ts,来自 commit 3(test(cli): deflake serve bridge wiring tests…)。#10552main 上用相反的方式修了同一个 flake——它删掉了这些逐测试超时,改成让包级上限感知 ECS(testTimeout: RUNNER_NAME startsWith 'ecs-qwen-' ? 60_000 : 15_000)。保留 commit 3 写死的 60_000 还会把本地与非 ECS 运行的上限一并抬高,而 main 在那里是刻意保持 15s 的。建议 rebase 时丢掉 commit 3,采用 main 的版本;功能本身可以干净地 rebase。

顺带一提:由于 PR 处于冲突状态,GitHub 从未生成 merge ref,因此本分支上根本没有跑过任何 pull_request workflow。按 732085dc5b 查询 workflow runs 只返回四个 pull_request_target 机器人流程,别无其他——这个 PR 上的绿勾是 label/assign/review 那条链路,不是 Qwen Code CI。这里没有来自 CI 的 lint、typecheck 或单测信号,这也是我在上面本地把这些闸门都跑一遍的原因之一。

F6 · Nit —— goalTokenBudgetReason 会渲染出 "(1 tokens)"。这个字符串来自 #9891,但正是本 PR 让个位数预算变得可设置,因此现在才可达。

结论

建议在以下处理之后合并:rebase 到 main 并丢掉 commit 3;刷新 PR 描述(F3)。F1 与 F2 改动都很小,集中在 packages/cli/src/config/config.ts 和两处描述文案,且都关乎一个消费上限如何失败——我更希望它们与功能一起落地,而不是留作后续;但两者都不是正确性缺陷,如果你更想先合并再单独跟进,我不会因此阻塞。

已在 Linux 验证;macOS 与 Windows 未在此验证。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix hit a verification-gate error before reaching a verdict (attempt 3/100) — it will retry on the next scan.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:

Address-review summary — PR #10543 (round 3)

Feedback triage

Finding Source Classification Decision
R2-1 — model.goalTokenBudget accepts any integer with no upper bound [rc:3889880240] automated reviewer (rv:5061297702) Optional (Suggestion), verified Implemented

No Required/Critical items, no failed checks, no deferred-feedback section in this round.

R2-1 — implemented

The claim was verified against the code before acting: isValidGoalTokenBudget accepted every non-negative integer up to Number.MAX_SAFE_INTEGER, while the sibling unattended-run budgets this PR's own docs table describes all carry typo guards (maxToolCalls caps at 1,000,000, maxWallTimeSeconds rejects values above ~24 days, maxSubagentDepth clamps to 1–100). A stray zero in settings.json could therefore silently disarm the runaway-spend guard this setting exists for, with no warning because the value was "valid". The reviewer's probe evidence (typo10x/typo100x accepted) matches the code at 732085dc5b.

Change (commit ee7a1ef5f7):

  • packages/core/src/config/config.ts — new exported GOAL_MAX_TOKEN_BUDGET = 10 * GOAL_DEFAULT_TOKEN_BUDGET (300,000,000, i.e. 10× the built-in default as the finding proposed; deriving it from the default keeps the "cap ≥ default" invariant structural). isValidGoalTokenBudget now rejects values above the cap, so they flow through the PR's existing fallback to `GOAL_DEFAULT_TOKEN_BUD
中文说明

🤖 AutoFix 在得出结论之前遇到验证门错误(第 3/100 次尝试)—— 将在下次扫描时重试。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/33323049660


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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix hit a verification-gate error before reaching a verdict (attempt 4/100) — it will retry on the next scan.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:

Autofix review round — PR #10543

This round merged origin/main (resolving the conflict per F5) and implemented F1, F2, F4 (the same change as automated finding R2-1), plus test coverage. F3 is drafted below for a maintainer to apply (this bot has no GitHub write access and the address-review flow has no PR-body mechanism). F6 is deferred to the follow-up queue (verified real, but outside this PR's footprint).

Feedback dispositions

F5 · merge conflict — resolved. Merged origin/main (40889ba). The only conflict was packages/cli/src/serve/server-default-bridge-wiring.test.ts: our commit 3 added per-test 60s timeouts, while main's #10552 fixed the same flake the opposite way — it removed the per-test timeouts and made the package ceiling ECS-aware (60_000 on ecs-qwen-* runners, 15_000 elsewhere). Resolution takes main's version (the merged, preferred fix); keeping ours would have raised the ceiling on local and non-ECS runs. The resolved file is byte-identical to origin/main.

F1 · invalid values now fail loud at startup — implemented. New resolveGoalTokenBudget(settings) in packages/cli/src/config/config.ts, symmetric with resolveMaxWallTimeSeconds / resolveMaxToolCalls: anything that is not a positive integer ≤ cap or -1 throws settings.json: model.goalTokenBudget must be a positive integer or -1 (no budget); got …, so fractional (1.5), negative (-5), non-numeric ("5000"), and above-cap values abort startup inst

中文说明

🤖 AutoFix 在得出结论之前遇到验证门错误(第 4/100 次尝试)—— 将在下次扫描时重试。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/33324729961


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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix hit a verification-gate error before reaching a verdict (attempt 5/100) — it will retry on the next scan.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:

Review round summary — PR #10543

Two commits this round: a merge of origin/main (the round was invoked with --conflict true) and one fix batch addressing the maintainer E2E review (ic:5470532565) and the automated reviewer's suggestion R2-1 (rc:3889880240).

1. Merge of origin/main (addresses F5)

  • The only conflict was packages/cli/src/serve/server-default-bridge-wiring.test.ts. This branch's deflake commit raised the per-test timeouts to 60s; main's ci: stabilize tests under shared ECS host contention #10552 fixed the same flake the opposite way — it removed the per-test timeouts and made the package-wide ceiling ECS-aware (60s on ecs-qwen-* runners, 15s elsewhere). The conflict was resolved by taking main's version, per wenshao's recommendation: keeping the per-test 60s would also raise the ceiling on local and non-ECS runs, where main deliberately keeps 15s. The merged bridge-wiring tests pass here (7/7, see Verification).
  • The workflow mandates additive commits, so commit 3 could not be dropped via rebase; the merge achieves the same final tree (the branch now carries main's approach for that file, byte-for-byte).
  • Merge note: the repository's pre-commit hook (lint-staged → prettier) normalized a pre-existing prettier violation in packages/cli/src/commands/serve.test.ts that arrived from main (324 lines re-wrapped, net +8). The hook's result was kept rather than bypassing the hook to preserve main's exact bytes. The file lives in the packages/cli workspace already covered by this PR's footprint
中文说明

🤖 AutoFix 在得出结论之前遇到验证门错误(第 5/100 次尝试)—— 将在下次扫描时重试。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/33338719051


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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix hit a verification-gate error before reaching a verdict (attempt 6/100) — it will retry on the next scan.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:

Autofix round — review feedback addressed

Commits this round (additive only):

  1. 4f32266ef6 — merge origin/main (conflict resolution per F5 below)
  2. 3db8e0629ffix(config): fail loud on invalid goalTokenBudget at startup (#10543)

F1 · Important — invalid values fell back silently, in the unsafe direction — FIXED

Reproduced first: new tests asserting startup rejection failed against the pre-fix code with promise resolved instead of rejecting for every one of 1.5, -5, "5000", 0, 300000000000 — the silent-fallback-to-30,000,000 behavior the finding describes.

Fix, symmetric with the neighbours as suggested: resolveGoalTokenBudget(settings) in packages/cli/src/config/config.ts now validates model.goalTokenBudget at startup through a new validateGoalTokenBudget in packages/cli/src/utils/runBudget.ts (next to validateMaxWallTimeSetting / validateMaxToolCalls) and throws settings.json: model.goalTokenBudget …; got …. Core's normalizeGoalTokenBudget is untouched and remains the lenient host-facing path (its tests, including the debug-warning pair, still pass). One deliberate deviation from the suggested message wording: because of F2 below, 0 is rejected too, so the messages say "must be > 0 (or -1 for no budget)" / "must be an integer (or -1 for no budget)" rather than "non-negative".

F2 · Important — 0 ambiguous with "no tokens allowed" — FIXED (drop-0 option)

Of the two options offered, this imple

中文说明

🤖 AutoFix 在得出结论之前遇到验证门错误(第 6/100 次尝试)—— 将在下次扫描时重试。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/33348016796


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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix stopped after 5 consecutive rounds that pushed nothing (failed rounds, timeouts, gate rejections, or stops under instruction). Retrying at the same per-round budget is not converging — this usually means the PR is too large or conflicts with a fast-moving main. A human should rebase, split, or reduce it, then comment @qwen-code /retry to re-arm. Until then future scans will skip this PR.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:
Qwen hit the tool-call loop guard during address-review. A human should take over this feedback batch.

See the Qwen Autofix agent step logs for model/tool output.

中文说明

🤖 AutoFix 已停止:连续 5 轮未能推送任何内容(失败轮次、超时、验证门拒绝或按指示停止)。以相同的单轮预算重试并不收敛 —— 这通常意味着 PR 过大,或与快速变动的 main 冲突。应由人工 rebase、拆分或缩减它,然后评论 @qwen-code /retry 重新武装。在此之前,后续扫描将跳过本 PR。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/33360112307


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

The setting's population is exactly "people typing zeros into a safety
bound", and an extra zero on the 30,000,000 default was accepted
silently, widening the runaway-spend window tenfold with no warning.
Positive values above GOAL_TOKEN_BUDGET_CAP (300,000,000) now fall back
to the default through the same invalid-value path as negatives, with
the debug-log warning. An operator who genuinely wants more autonomy
than 300M tokens per window has the explicit opt-out (0/-1); the cap is
a typo guard, not a policy on long runs.

Mutation probe: removing the upper bound fails exactly the new
cap-boundary test (595 others green).
@qwen-code-dev-bot qwen-code-dev-bot added the autofix/needs-human The autofix loop stopped on this PR — a human must re-arm, split, merge, or close it label Aug 31, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

⏸️ Takeover paused: this PR reached its round cap (100/100). Comment @qwen-code /takeover to re-arm a fresh window and continue management, or @qwen-code /takeover stop to release.

中文说明

⏸️ 托管已暂停:本 PR 达到轮次上限(100/100)。评论 @qwen-code /takeover 可重新武装、开启新窗口继续托管;或评论 @qwen-code /takeover stop 释放。

@qqqys

qqqys commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author

@wenshao 这一轮已处理并普通推送到 4e235f1d58

  • F1/F2:CLI 现在会在启动时校验 model.goalTokenBudget;正整数(上限 300M)与 -1 合法,0、其他负数、小数、非数值和超上限值直接以 settings.json 错误失败,不再静默回退。Core 的 host-facing 宽松归一化路径保留,因此直接集成方既有的 0 opt-out 不受影响。
  • F3:PR 正文的英文/中文语义、风险说明和验证数字已刷新。
  • F4:300M typo guard 已在前一 head 修复,本轮补齐 CLI 启动层的同边界校验。
  • F5:已合并 live main (be5f00eb80);冲突测试文件取主线版本并确认与 main 字节一致,保留 ci: stabilize tests under shared ECS host contention #10552 的 ECS-aware package timeout。
  • F6:1 tokens 来自 feat(goal): stop autonomous continuation at a token budget the user re-arms #9891 的既有文案,本轮不扩大范围,后续单独清理。

验证:CLI 聚焦测试 475/475,core config 596/596,仓库级 build 与 typecheck 均通过;PR 现已恢复 MERGEABLE。

@qqqys

qqqys commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔄 Takeover re-armed: the round counter starts a fresh window (previous rounds no longer count toward the cap); management continues.

中文说明

🔄 已重新武装:轮次计数开启新窗口(此前轮次不再计入上限),托管继续。

@qwen-code-dev-bot qwen-code-dev-bot removed the autofix/needs-human The autofix loop stopped on this PR — a human must re-arm, split, merge, or close it label Aug 31, 2026

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

Reviewed.

1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

  • R3-1 goalTokenBudget fallback warning wording — dropped as location overlap at packages/core/src/config/config.ts:2618, already reported (comment 3888683781, R1-1 thread)

Not explored to full depth (tool budget reached): "agent 2": none — no checks were cut short.; "agent 5": none — no check was cut short..

中文说明

已审查。

本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未探索到全部深度(达到工具调用预算):"agent 2"none — no checks were cut short."agent 5"none — no check was cut short.

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

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No blocking findings.

Checked:

  • Full config chain: settings.json → validateGoalTokenBudget (CLI) → resolveGoalTokenBudget → loadCliConfig → Config.goalTokenBudgetGrant → GoalRuntime.tokenBudgetGrant — wired and tested end-to-end
  • Two-layer validation design is intentional and consistent: CLI layer (runBudget.ts) rejects 0 at startup with a clear error; Config layer (config.ts) accepts 0/-1 as the opt-out alias and normalizes to Infinity — matching the sibling budget settings pattern
  • GOAL_TOKEN_BUDGET_CAP = 300_000_000 (10x default) acts as typo guard — tested at both cap boundary and cap+1
  • Debug-log warning for invalid values fires only for invalid/unknown inputs (not for valid, absent, or opt-out) — tested explicitly
  • normalizeGoalTokenBudget and isValidGoalTokenBudget exported correctly; schema in settings.schema.json and docs in settings.md updated consistently

CI: 14 checks green — ubuntu build+test (30m), integration, E2E smoke, CVE audit, secret scan, desktop shells all pass. macOS/Windows test jobs skipped (consistent with other PRs).

Cross-check: All bot comments are [Suggestion] only, no CHANGES_REQUESTED. R1-2 ('no test for warning branch') is stale — the debug-log warning test is present. R2-1 ('no upper bound') is stale — GOAL_TOKEN_BUDGET_CAP is implemented.

Reviewed with AI assistance.

@qwen-code-dev-bot qwen-code-dev-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.

Gate APPROVE at head 4e235f1d. Validation is layered the right way: settings.json values are startup-rejected on 0/fraction/overshoot/non-number with clear messages, the core normalization stays lenient for host integrations (0/-1 → unbounded, invalid → default + warn), the 300M cap is shared from core so CLI and runtime cannot disagree, and the grant is wired straight into the Goal runtime's tokenBudgetGrant at construction — runtime semantics unchanged. 0 unresolved threads, Test (ubuntu) and all substantive checks green, chiga0 approved this exact head. 中文:门禁通过。CLI 启动校验 + core 宽松归一双层设计合理,cap 常量单一来源,grant 直接入 Goal runtime;线程零未解决、CI 全绿。

@qqqys
qqqys enabled auto-merge August 31, 2026 11:21

@yiliang114 yiliang114 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 at 4e235f1. Validation is layered cleanly: the CLI rejects 0/fractional/out-of-range at startup with actionable messages (-1 = unlimited, cap at 10x default as a typo guard), while core's normalizeGoalTokenBudget stays tolerant for programmatic Config use — the lenient 0/-1 branch is unreachable from settings.json since the CLI validator sits upstream. Schema, docs, and ~268 test lines all consistent; CI green, 0 unresolved threads.

@qqqys
qqqys added this pull request to the merge queue Aug 31, 2026
Merged via the queue into QwenLM:main with commit 12343f2 Aug 31, 2026
109 checks passed
pull Bot pushed a commit to bit-cook/qwen-code that referenced this pull request Sep 1, 2026
…AME (QwenLM#10671)

* fix(autofix): clamp gate test load explicitly instead of via RUNNER_NAME

The verification gate launches through an env -i allowlist that drops
RUNNER_NAME, so the vitest configs' ECS load clamps (60s test/hook
timeouts, maxWorkers 25%) silently deactivate inside the gate: tests run
with 15s timeouts, unbounded workers and coverage collection on a host
shared with other autofix jobs. Under pool saturation this produced both
false rejections (QwenLM#10171 round 3: 73 load-induced 15s timeouts in files
the PR never touched, charged to the round) and gate deaths past the
step's 60-minute cap that discarded verified fixes ("verification-gate
error": QwenLM#10171 rounds 1/2/5-7, QwenLM#10543 five in a row).

Pass the clamp values explicitly on both gate vitest invocations (the
per-package --changed run and the bite check) so the verdict does not
depend on env plumbing or runner naming, and disable coverage: nothing
in the gate consumes it, and its collection dominated the overrun
(72,000 CPU-seconds of collect in one 1,560s gate leg).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhZA7LdQXZjcjfiPsoZkqZ

* fix(autofix): clamp the gate's third vitest leg and pin the clamps

Addresses review round 1 on QwenLM#10671.

R1-6 The contracts check runs a web-shell vitest from inside the gate's
own `env -i` child, and web-shell's config sets no timeouts at all, so
that leg ran at vitest's 5s default on the same saturating host — the
false-rejection class this PR removes, surviving in a sibling path. The
clamp array moves above the contracts call and is handed to the shared
script through AUTOFIX_VITEST_FLAGS; the issue-fix gate invokes the same
script where RUNNER_NAME is present and leaves the variable unset, so its
invocation is unchanged.

R1-3 Nothing pinned that the clamps reach any invocation — every existing
assertion is a prefix that ends before the expansion, so dropping it from
a leg (or emptying the array, silent without `set -u`) stayed green while
the gate reverted to 15s timeouts and coverage on. Three structural pins
added on the review runner only, plus a contracts-script case that runs
with AUTOFIX_VITEST_FLAGS set and asserts the flags reach npm.

R1-4 The array hand-copies the ECS branch of three vitest configs, and
inside the gate the CLI flags outrank the config — so raising an ECS
ceiling to shelter a heavier test would leave the gate enforcing the old
one and rejecting a fix that is green in normal CI. A parity test in
scripts/tests/unit-vitest-configs.test.ts re-imports core, cli and
acp-bridge under a stubbed ecs-qwen RUNNER_NAME (they read the env at
import time) and asserts equality with the array parsed out of the shell
script.

R1-5 Narrowed the comment's claim, per the finding's own minimum. The
residual is real and now named in the script: a handful of test files set
their ceiling with a runtime `vi.setConfig` keyed on RUNNER_NAME, which
outranks the CLI, so they keep their non-ECS values in here. Closing it
needs a gate sentinel on both env -i allowlists plus a change in each
file — a separate slice, not folded into this one.

R1-1 is declined; see the thread. Its two premises did not reproduce
against the lockfile-pinned vitest 1.6.1 under packages/sdk-typescript:
the full suite passes with --maxWorkers=25% (37 files, 1747 tests,
exit 0), and --maxThreads is rejected by 1.6.1 and 3.2.7 alike, so
--maxWorkers is the spelling both majors accept rather than neither.

* fix(autofix): pin the clamp witnesses and correct the unclamped-leg record

Addresses review round 2 on QwenLM#10671; re-verifies round 1's fixes with
mutation probes.

R2-1 The comments justifying the unclamped issue-fix leg rested on a
premise that does not hold for that leg: web-shell's vitest config sets
no timeouts and has no RUNNER_NAME branch, so the drift test runs at
vitest's 5s default wherever it runs. Corrected at all four mirror
sites (both scripts and both test comments): the review gate passes
explicit clamps; the issue-fix gate and repo-hygiene's docker leg —
the previously unnamed third caller — invoke the contracts script
without the variable and accept the 5s default. The alternative
(exporting AUTOFIX_VITEST_FLAGS in the issue-fix gate step) edits a
workflow file this PR has never touched and stays out of scope.

R2-2 --maxWorkers=25% is coerced to NaN by vitest 1.x; the
lockfile-pinned 1.6.1 under packages/sdk-typescript survives only
because its config sets a numeric poolOptions.threads.maxThreads,
which tinypool reads before ctx.config.maxWorkers. Pin the shield:
a new case derives vitest-1.x workspaces from nested lockfile copies
and asserts each keeps the threads pool and a numeric maxThreads,
failing with a directive if such a workspace is missing from the
config registry. Mutation-verified red on shield removal.

R2-3 The export is the only line carrying the clamps across the
process boundary into check-autofix-contracts.sh; nothing pinned it.
Added the structural pin plus an ordering assertion against the
contracts call — deleting the export or moving it below the call now
fails the suite. Both mutants verified red.

R2-4 The contracts case's fake npm logged $*-joined argv, rendering a
joined-blob flag byte-identically to separate words; the [*]-for-[@]
mutant survived. The shim now logs one bracketed line per argv word
and the four expectations in the case were updated; the mutant now
fails.

R1-3/R1-4/R1-5/R1-6 (round-2 commit) re-verified with mutation
probes: dropping either invocation's expansion, dropping the
assignment, emptying the array, drifting --testTimeout to 61000, and
dropping the flag expansion inside the contracts script each turn an
existing witness red.

R1-1 remains declined: the deterministic crash does not reproduce at
this head (the leg passes with sdk's shield present), but the round-2
rationale was wrong and is corrected on the thread; the residual risk
is the shield R2-2 now pins.

* test(autofix): pin both sides of the gate's AUTOFIX_VITEST_FLAGS transport

Addresses review round 3 on QwenLM#10671; both pins mutation-verified.

R3-1 Nothing pinned the VITEST_LOAD_CLAMPS definition above its
consumers: the existing pins are position-blind (toContain here, the
parity regex in unit-vitest-configs.test.ts matches anywhere), so
moving the array below its consumers left every pin green while bash
expanded the then-unset array to zero words under the gate's
`set -eo pipefail` without `-u` — AUTOFIX_VITEST_FLAGS goes empty and
the package and bite legs lose all four clamps, silently reverting to
the incident conditions. Added an explicit ordering pin against the
star-join, the first consumer in script order, which pins the
definition above every consumer. Outright deletion was already caught
by the parity test's existence assertion; the move was the only
surviving hole. Move mutant verified red (61668 < 34052 fails).

R3-2 The remove side was pinned nowhere: moving `unset
AUTOFIX_VITEST_FLAGS` above the contracts call (or deleting it)
strips the export the drift leg inherits at child-spawn time, leaving
the web-shell drift test at vitest's 5s default with every
establish-side pin green. Added the symmetric ordering pin, contracts
call before unset. Move and delete mutants both verified red
(34333 < 34241 and 34306 < -1 fail).

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: 易良 <1204183885@qq.com>
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.

7 participants