perf(ci): make the triage budget operator-tunable and raise it - #8810
Conversation
The triage job's fixed 30-minute cap was killing every substantial PR's triage at exactly the budget — five observed (8723, 8726, 8731, 8764, 8801), each a full agent run discarded — and the successful-run distribution shows why: 22 jobs sampled ran median 5.8m / p90 22.3m / max 29.5m. A max just under the cap is a distribution being clipped, not one that fits. Back the budget with QWEN_TRIAGE_TIMEOUT_MINUTES (fallback 60), the same pattern as the autofix fleet caps in #8731 and probe-verified there for expression-valued job fields. Triage is advisory: the worst case a longer budget risks is one held runner, while a killed run wastes the same runner and the work it was about to publish. Pinned by a test asserting the variable-backed form with its fallback; reverting to the fixed literal fails it.
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not explored to full depth (tool budget reached): This PR makes the qwen-triage workflow's timeout-minutes...: full vitest execution of scripts/tests/qwen-triage-workflow.test.js — neither the worktree nor the parent checkout has node_modules , so instead of installin…. Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-triage.yml`: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
已审查。 建议见行内评论。 未探索到全部深度(达到工具调用预算):This PR makes the qwen-triage workflow's timeout-minutes...:full vitest execution of scripts/tests/qwen-triage-workflow.test.js — neither the worktree nor the parent checkout has node_modules , so instead of installin…。 未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-triage.yml`: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max via Qwen Code /review (v0.21.8)
wenshao
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-triage.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
已审查。 建议见行内评论。 未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-triage.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— deepseek-v4-flash via Qwen Code /review (v0.21.8)
Both findings verified before fixing. The knob was unvalidated, and the failure mode is nasty precisely because the knob exists to be changed WITHOUT a PR: a malformed repository variable — '60 minutes', '1h', a 6O typo, quotes, a decimal — would make fromJSON throw at expression time, killing the triage job before any step runs: no always() finalize, no status comment, a silent stop whose run log blames fromJSON rather than the variable. And '0' parses fine into an instantly-cancelled job. timeout-minutes cannot be validated by the job that consumes it, so the authorize job (which triage already needs, and which can run bash) sanitizes it: bare-integer check, floor 10, ceiling 600, every rejection warning with the VARIABLE's name. The triage job consumes the sanitized output; || 60 covers the events where authorize is skipped. The pin was an unanchored substring over raw YAML, satisfied by a commented-out line while the job silently inherits GitHub's 360-minute default — the review probe-verified that mutant surviving. The pin now matches active lines only, and the sanitize step is replayed verbatim against the review's whole enumeration: unset, sane, zero, runaway, and five malformed shapes, each asserted to fall back and name the knob. Mutation-tested, 3 of 3 caught: the timeout line commented out, the floor clamp dropped, the integer check bypassed.
|
@qwen-code /takeover |
|
🤝 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. Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 |
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
|
🤖 AutoFix ran out of time before finishing (timeout (7200000ms)) (attempt 1/100) — it will retry on the next scan. What I found before stopping: See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/31327011711 🧠 Handled by Qwen Code · model/模型 |
|
Qwen Code review timed out. Qwen review timed out after 10800 seconds (of the 180-minute budget). For large PRs, retry with a longer timeout by commenting: |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: AutoFix — no new code changes this round (address-review for #8810) The previous round committed and pushed the fixes for both review findings (
Audit note: the empty-output path ( Verification
No commits this round; the working tree is left exactly as pushed. 中文说明AutoFix —— 本轮无新代码改动(#8810 的 address-review) 上一轮已提交并推送了两个评审意见的修复(
审计说明:空输出路径(issues / 纯数字 dispatch 事件下 authorize 被跳过时的 验证
本轮无提交;工作树保持已推送的原状。 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
🔀 Base updated: red check(s) [review-pr] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round: no actionTriage summaryThe new feedback since the last evaluation contained a single item, and there were no actionable findings:
There were no reviews with findings, no inline comments, and no failed or still-red checks in this round. ResultNo code changes were made and no commit was created. The PR remains at its previous head ( 中文说明Autofix 评审轮次:无操作分诊摘要自上次评估以来的新反馈仅包含一条,且没有任何可执行的发现:
本轮不存在带有发现的评审、行内评论,也没有失败或持续红色的检查。 结果未做任何代码变更,未创建提交。PR 保持在原 head( Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not explored to full depth (tool budget reached): PR #8810 (QwenLM/qwen-code) makes the qwen-triage workflo...: running scripts/tests/qwen-triage-workflow.test.js (worktree has no node_modules ; npm install not attempted — the 9-spawn cost is trivially bounded by ins…; PR #8810 (QwenLM/qwen-code) makes the qwen-triage workflo...: full job-level reconstruction of the 22-job duration distribution (exact historical sample not rebuilt). Not reviewed: reverse audit — stopped before round 4 by the review time budget. Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-triage.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
已审查。 建议见行内评论。 未探索到全部深度(达到工具调用预算):PR #8810 (QwenLM/qwen-code) makes the qwen-triage workflo...:running scripts/tests/qwen-triage-workflow.test.js (worktree has no node_modules ; npm install not attempted — the 9-spawn cost is trivially bounded by ins…;PR #8810 (QwenLM/qwen-code) makes the qwen-triage workflo...:full job-level reconstruction of the 22-job duration distribution (exact historical sample not rebuilt)。 未审查:反向审计——评审时间预算不足,未能开始第 4 轮。 未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-triage.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| MINUTES=60 | ||
| if [[ -n "${RAW}" ]]; then | ||
| if [[ "${RAW}" =~ ^[0-9]+$ ]]; then | ||
| MINUTES=$((10#${RAW})) |
There was a problem hiding this comment.
[Suggestion] The sanitize step's $((10#${RAW})) silently wraps on ≥19-digit inputs (bash 64-bit arithmetic overflow). A wrapped residue landing inside [10,600] skips both clamp branches and is emitted with no ::warning:: — the one malformed-shape class where the sanitizer's diagnostics go dark. Probe-reproduced on bash 5.2.21: QWEN_TRIAGE_TIMEOUT_MINUTES=92233720368547758180 matches ^[0-9]+$, wraps to 100, and the step writes triage_timeout_minutes=100 with no warning. (Negative-wrap and >600-wrap residues still hit the clamps with warnings, so the runner-holding bound itself is never defeated — hence Suggestion, not Critical.) — Failure scenario: a ≥19-digit repo-variable value → silent unwarned timeout in [10,600] instead of the clamped 600 with a warning → the one input class where a misconfiguration leaves no trace in the run log this step exists to provide.
| MINUTES=$((10#${RAW})) | |
| if (( ${#RAW} > 18 )); then | |
| echo "::warning::QWEN_TRIAGE_TIMEOUT_MINUTES=${RAW} exceeds the 600-minute ceiling; clamping to 600." | |
| MINUTES=600 | |
| else | |
| MINUTES=$((10#${RAW})) | |
| fi |
中文说明
sanitize 步骤的 $((10#${RAW})) 在 ≥19 位数字输入时会静默回绕(bash 64 位算术溢出)。回绕结果若落在 [10,600] 区间内,两个 clamp 分支都不会触发,输出也就没有 ::warning::——这是 sanitizer 诊断唯一"失声"的非法值类别。已在 bash 5.2.21 上探针复现:QWEN_TRIAGE_TIMEOUT_MINUTES=92233720368547758180 能通过 ^[0-9]+$,回绕为 100,步骤写出 triage_timeout_minutes=100 且无任何告警。(回绕为负数或 >600 的残值仍会被 clamp 捕获并告警,runner 占用上界本身不会被击穿——因此是 Suggestion 而非 Critical。)失败场景:仓库变量被设为 ≥19 位数字 → 静默产生 [10,600] 内无告警的超时值,而不是钳制到 600 并告警 → 这是本步骤的存在意义(运行日志可追溯配置错误)唯一失效的输入类别。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| - name: 'Sanitize the triage budget' | ||
| id: 'budget' | ||
| env: | ||
| RAW: '${{ vars.QWEN_TRIAGE_TIMEOUT_MINUTES }}' |
There was a problem hiding this comment.
[Suggestion] The single line that connects the repository variable to the sanitize script is pinned by no test — the replay test injects RAW itself (env: { ...process.env, RAW: raw }) and only reads budget.run, never budget.env. Probe-verified: renaming the variable in this mapping leaves both new tests passing. — Failure scenario: a future edit typos or drops QWEN_TRIAGE_TIMEOUT_MINUTES in this mapping → GitHub expands a nonexistent vars.* to the empty string, the script's explicitly silent default branch fires → the knob this PR exists to provide dies with no warning, every run gets 60, and the suite stays green. Fix in the replay test (scripts/tests/qwen-triage-workflow.test.js), where budget is already parsed:
expect(budget.env.RAW).toBe("${{ vars.QWEN_TRIAGE_TIMEOUT_MINUTES }}");中文说明
把仓库变量接到 sanitize 脚本的这唯一一行没有任何测试钉子——回放测试自己注入 RAW(env: { ...process.env, RAW: raw }),只读 budget.run,从不读 budget.env。已用探针验证:把这里映射中的变量名改成拼错的,两个新测试仍然全绿。失败场景:未来某次改动把这处的 QWEN_TRIAGE_TIMEOUT_MINUTES 拼错或删掉 → GitHub 把不存在的 vars.* 展开为空串,走进脚本显式的静默兜底分支 → 本 PR 要提供的旋钮无声失效,每次运行都拿 60,而测试套件仍是绿的。修复加在回放测试(scripts/tests/qwen-triage-workflow.test.js)中 budget 已解析的位置。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| warned: false, | ||
| }); | ||
| // 0 would be an instantly-cancelled job — clamped to the floor | ||
| expect(runBudget('0')).toMatchObject({ |
There was a problem hiding this comment.
[Suggestion] The exact clamp boundaries (10, 600) are never exercised, so comparator flips survive the suite. Probe-verified: mutating (( MINUTES < 10 )) to (( MINUTES <= 10 )) passes both tests, and replaying the mutant shows input 10 then emits a spurious "clamping to 10" warning for a value that was not clamped (the > → >= flip at 600 is symmetric). — Failure scenario: with a flipped comparator, an operator setting exactly the floor/ceiling gets a misleading ::warning:: claiming a clamp that did not happen, and nothing in the suite catches it.
| expect(runBudget('0')).toMatchObject({ | |
| // exactly at the boundaries: no clamp, no warning | |
| expect(runBudget('10')).toMatchObject({ | |
| out: 'triage_timeout_minutes=10', | |
| warned: false, | |
| }); | |
| expect(runBudget('600')).toMatchObject({ | |
| out: 'triage_timeout_minutes=600', | |
| warned: false, | |
| }); | |
| expect(runBudget('0')).toMatchObject({ |
中文说明
clamp 的精确边界值(10、600)从未被验证,因此比较符翻转能逃过整个套件。已用探针验证:把 (( MINUTES < 10 )) 变异为 (( MINUTES <= 10 )),两个测试仍然通过;回放该变异体可见输入 10 会多出一条"clamping to 10"的冤枉告警(值并未被钳制;600 处 > → >= 的翻转对称同理)。失败场景:比较符翻转后,运维恰好把变量设到边界值时会收到一条声称发生了钳制的误导性 ::warning::,而套件无法发现。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| warned: false, | ||
| }); | ||
| // a sane value passes through untouched | ||
| expect(runBudget('90')).toMatchObject({ |
There was a problem hiding this comment.
[Suggestion] The deliberate 10# decimal guard has no test — no leading-zero input is supplied. Probe-verified: mutating MINUTES=$((10#${RAW})) to MINUTES=$((${RAW})) leaves the suite green, yet bash then parses a padded value like 060 as octal → 48. — Failure scenario: an operator sets 060; without the 10# guard the job silently runs with 48 minutes instead of 60 (in range, no warning), and the suite cannot catch the guard's removal. Add next to the passthrough case (after this block):
// a padded value must stay decimal — bash parses 060 as octal (48) without 10#
expect(runBudget('060')).toMatchObject({
out: 'triage_timeout_minutes=60',
warned: false,
});中文说明
刻意加上的 10# 十进制防护没有测试——套件没有提供任何带前导零的输入。已用探针验证:把 MINUTES=$((10#${RAW})) 变异为 MINUTES=$((${RAW})),套件仍然全绿,而 bash 会把 060 这样的补零值按八进制解析 → 48。失败场景:运维设了 060;一旦失去 10# 防护,job 会静默地按 48 分钟而不是 60 分钟运行(在合法区间内、无告警),且套件无法发现该防护被移除。在上方的直通用例之后补一个用例即可。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| const budget = doc.jobs.authorize.steps.find((s) => s.id === 'budget'); | ||
| expect(budget).toBeTruthy(); |
There was a problem hiding this comment.
[Suggestion] Neither new test pins that the budget step runs unconditionally — the replay test locates the step by id and executes its run without ever reading its if, and the substring pins cover only the outputs mapping and timeout lines. Probe-verified: adding if: github.event_name == 'pull_request_target' to the step survives the entire 131-test suite; expect(budget.if).toBeUndefined(); kills the mutant. (Distinct severance point from the env: RAW mapping pin suggested on the workflow file.) — Failure scenario: a future refactor scopes the step to one event → on /triage- or /verify-comment events the authorize job still runs but the step is skipped, its output is empty, and the consuming '' || 60 silently yields 60 — the knob is dead exactly on the lanes where authorize runs and the sanitized value was meant to apply, with no warning (the warning machinery lives in the bash that never runs).
| const budget = doc.jobs.authorize.steps.find((s) => s.id === 'budget'); | |
| expect(budget).toBeTruthy(); | |
| const budget = doc.jobs.authorize.steps.find((s) => s.id === 'budget'); | |
| expect(budget).toBeTruthy(); | |
| expect(budget.if).toBeUndefined(); |
中文说明
两个新测试都没有钉住 budget 步骤是无条件运行的——回放测试按 id 找到该步骤并执行其 run,但从不读它的 if;子串钉子也只覆盖 outputs 映射行和 timeout 行。已用探针验证:给该步骤加上 if: github.event_name == 'pull_request_target',整个 131 条测试的套件仍然全绿;加上 expect(budget.if).toBeUndefined(); 即可杀死该变异体。(与工作流文件上建议的 env: RAW 映射钉子是不同的切断点。)失败场景:未来某次重构把该步骤限定到某一事件 → 在 /triage 或 /verify 评论事件上 authorize job 仍会运行但该步骤被跳过,其输出为空,消费端 '' || 60 静默取 60——旋钮恰好在 authorize 运行、本应使用 sanitized 值的通道上失效,且无任何告警(告警机制在从未运行的 bash 里)。
— qwen3.8-max via Qwen Code /review (v0.21.8)
A repository variable with more than 18 digits overflows bash's 64-bit arithmetic, and the wrapped residue can land inside [10,600], silently skipping both clamps with no warning — the one malformed shape where the sanitizer's diagnostics go dark. Check the digit count before the arithmetic and clamp such values to the ceiling with a warning. Also pin the seams the replay test cannot see: which repository variable feeds RAW, that the sanitize step is unconditional, the exact clamp boundaries, the 10# decimal guard, and the overflow clamp.
|
🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下: Address review round — PR #8810All five inline findings from the round-2 automated review were addressed in one commit ( Findings and dispositions
The review-level notes about depth cut short by the tool budget (node_modules install for the replay run, full 22-job duration reconstruction, reverse audit) are observations, not findings; no action required. The replay tests execute the real sanitize bash from the parsed workflow, so the fix and all new pins are verified against the actual step code, not a copy. VerificationCommands actually run, in order (final tree state is byte-identical to the state these passed on — see note):
Note: during the mutant check an incidental 中文说明评审处理轮次 — PR #8810第 2 轮自动评审的全部 5 条行内发现已在一次提交中处理完毕( 发现与处理结果
评审总结中关于工具预算导致未深入的部分(回放运行所需的 node_modules 安装、22 个 job 时长分布的完整重建、反向审计)属于观察记录,不是发现,无需处理。回放测试执行的是从工作流解析出的真实 sanitize bash,因此修复与所有新钉子都是针对真实步骤代码验证的,而非副本。 验证实际执行的命令及结果(最终工作树与这些命令通过时的状态逐字节一致——见下方说明):
说明:变异检查期间一次附带的 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/模型 |
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-triage.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
已审查。 建议见行内评论。 未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-triage.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— claude-opus-4-6 via Qwen Code /review (v0.21.4)
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round — no code changesRound inputs: one automated inline Suggestion plus one informational review note; no failed or still-red checks, no conflict. The single finding was verified against the code and declined with evidence, so the PR head is unchanged at Dispositions
Verification
No commit was made; nothing is resolved in code, so no inline thread is marked resolved (the declined finding stays open with its reply). 中文说明Autofix 审查轮次 —— 无代码变更本轮输入:一条自动化行内 Suggestion,外加一条信息性审查备注;无失败或持续失败的检查,无冲突。该唯一 finding 已对照代码核实并以证据拒绝,因此 PR head 保持不变,仍为 处理结果
验证
未产生提交;代码中没有任何被解决的内容,因此没有行内线程被标记为已解决(被拒绝的 finding 保持打开,并附有回复)。 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not explored to full depth (tool budget reached): This PR makes the qwen-triage workflow's triage-job time...: actionlint is not installed on this runner, so no expression-level lint of the workflow was performed (YAML validity is nevertheless proven by the parse(workfl…. Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-triage.yml`: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
已审查。 建议见行内评论。 未探索到全部深度(达到工具调用预算):This PR makes the qwen-triage workflow's triage-job time...:actionlint is not installed on this runner, so no expression-level lint of the workflow was performed (YAML validity is nevertheless proven by the parse(workfl…。 未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-triage.yml`: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| if (( ${#RAW} > 18 )); then | ||
| echo "::warning::QWEN_TRIAGE_TIMEOUT_MINUTES=${RAW} exceeds the 600-minute ceiling; clamping to 600." |
There was a problem hiding this comment.
[Suggestion] The ${#RAW} > 18 length guard measures string width, not numeric width, while the ^[0-9]+$ regex admits leading zeros — contradicting the inline comment "Any such value is above the ceiling by construction". — Failure scenario: operator sets QWEN_TRIAGE_TIMEOUT_MINUTES=000000000000000000060 (21 chars, true value 60); probe against the real script: output is triage_timeout_minutes=600 — a 10x timeout versus intent — with a false "exceeds the 600-minute ceiling" warning pointing at the wrong diagnosis; 22 zeros (value 0) yields 600 instead of the floor 10. Impact stays bounded to [10,600] and a warning fires, but padding is exactly the trap class the 10# guard shows the author cared about.
Suggested fix (strip leading zeros before the width test — probe-verified to restore 60/no-warning, floor-clamp all-zeros, and preserve the 20-significant-digit wrap protection):
TRIMMED="${RAW#"${RAW%%[!0]*}"}"
TRIMMED="${TRIMMED:-0}"
if (( ${#TRIMMED} > 18 )); then
...
fi
MINUTES=$((10#${TRIMMED}))中文说明
[Suggestion] ${#RAW} > 18 长度守卫衡量的是字符串宽度而非数值宽度,而 ^[0-9]+$ 正则允许前导零——与行内注释 "Any such value is above the ceiling by construction"(任何这样的值按构造都高于上限)矛盾。——失败场景:运维把 QWEN_TRIAGE_TIMEOUT_MINUTES 设为 000000000000000000060(21 个字符,真实值 60);对真实脚本探针验证:输出 triage_timeout_minutes=600——是预期超时的 10 倍——并伴随一条指向错误诊断的假警告 "exceeds the 600-minute ceiling";全零(数值 0)会得到 600 而非下限 10。影响仍在 [10,600] 内且会告警,但补零正是 10# 守卫所体现的作者在意的那类陷阱。建议修复:在宽度判断前先去掉前导零(已探针验证:恢复 60/无告警、全零走下限钳制、保留 20 位有效数字的溢出保护)。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| elif (( MINUTES > 600 )); then | ||
| echo "::warning::QWEN_TRIAGE_TIMEOUT_MINUTES=${RAW} exceeds the 600-minute ceiling; clamping to 600." | ||
| MINUTES=600 |
There was a problem hiding this comment.
[Suggestion] The 600-minute ceiling is only honored on the self-hosted ECS pool: GitHub-hosted runners hard-cap any job at 6 hours (360 minutes), so the knob's validated range (10..600) is silently narrowed to 10..360 on every run routed to ubuntu-latest — fork-author PRs, /triage comment reruns targeting them, plain-number dispatch, comments on plain issues, and kill-switch-on runs. — Failure scenario: operator sets QWEN_TRIAGE_TIMEOUT_MINUTES=480; a same-repo PR triage on ECS gets 480 minutes, but a fork-author PR triage on ubuntu-latest dies silently at 360 minutes — against the "validated, clamped 10..600" contract this comment block states — and the failure looks identical to the pre-PR cap kills this change set out to eliminate. Platform fact verified against GitHub's docs (hosted max job execution 6h; self-hosted 5 days); lines 1844 and 3519 of this file already treat 360 minutes as the hosted-runner bound. Suggested fix: lower the ceiling to 360, or note in the ceiling comment that values above 360 only take effect on the self-hosted pool.
中文说明
[Suggestion] 600 分钟上限只在自托管 ECS 池上生效:GitHub 托管 runner 把任何 job 硬性限制在 6 小时(360 分钟),因此对路由到 ubuntu-latest 的运行(fork 作者的 PR、针对此类 PR 的 /triage 评论重跑、纯数字 dispatch、纯 issue 上的评论、kill-switch 打开时的运行)来说,旋钮的合法范围(10..600)被悄悄收窄为 10..360。——失败场景:运维把 QWEN_TRIAGE_TIMEOUT_MINUTES 设为 480;同仓库 PR 在 ECS 上获得 480 分钟,而 fork 作者 PR 在 ubuntu-latest 上于 360 分钟被静默杀死——与本注释块声明的 "validated, clamped 10..600" 契约相悖——且该失败与本改动要消除的"撞旧上限而死"看起来一模一样。平台事实已对照 GitHub 官方文档核实(托管最长 6 小时、自托管 5 天);本文件 1844/3519 行已把 360 分钟视为托管 runner 的界限。建议修复:把上限降为 360,或在钳制注释中注明高于 360 的值只在自托管池生效。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| MINUTES=60 | ||
| if [[ -n "${RAW}" ]]; then | ||
| if [[ "${RAW}" =~ ^[0-9]+$ ]]; then |
There was a problem hiding this comment.
[Suggestion] This sanitize recipe is a second inline copy of the block in qwen-autofix.yml (~4406-4435) for QWEN_TIMEOUT_MS, and the two have already diverged: autofix bounds width in the regex (^[0-9]{1,8}$) and clamps garbage to the ceiling (fail-high); this copy uses an unbounded regex + a separate ${#RAW} > 18 branch and falls garbage back to the default (fail-default). A third consumer (qwen-code-pr-review.yml:379) skips sanitizing entirely (pre-existing raw fromJSON(vars.X)). — Failure scenario: when the next boundary bug is found in one copy — this is fiddly octal/int64 arithmetic that both copies' comments attest got things wrong before — the fix must be re-derived and re-applied to the other copy by hand, and the next tunable knob pastes a third copy or skips sanitizing entirely — the exact silent-kill failure mode this PR exists to prevent. Suggested fix: extract a shared .github/scripts/sanitize-budget.sh (args: raw value, floor, ceiling, default) called by both steps, or at minimum align the overflow-guard mechanism / cross-reference the two blocks.
中文说明
[Suggestion] 本 sanitize 配方是 qwen-autofix.yml 中 QWEN_TIMEOUT_MS 代码块(约 4406–4435 行)的第二份内联拷贝,且两者已经分化:autofix 在正则中限定宽度(^[0-9]{1,8}$)并把垃圾值钳到上限(fail-high);本拷贝使用无界正则 + 独立的 ${#RAW} > 18 分支,并把垃圾值回落到默认值(fail-default)。第三个消费方(qwen-code-pr-review.yml:379)完全跳过了消毒(既有的裸 fromJSON(vars.X))。——失败场景:当下一处边界 bug 在其中一份拷贝中被发现——这是棘手的八进制/int64 算术,两份拷贝的注释都承认曾经写错过——修复必须手工重新推导并应用到另一份;下一个可调旋钮则会粘贴第三份拷贝,或干脆跳过消毒——那正是本 PR 要防范的静默杀死失败模式。建议修复:抽取共享的 .github/scripts/sanitize-budget.sh(参数:原始值、下限、上限、默认值)供两个步骤调用,或至少对齐两处的溢出守卫机制/互相交叉引用。
— qwen3.8-max via Qwen Code /review (v0.21.8)
|
🤖 AutoFix updated a stale base — the fix did not pass verification, but this PR was behind What I found before stopping: See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/31346879191 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not explored to full depth (tool budget reached): PR #8810 makes the qwen-triage workflow's triage-job tim...: actionlint is not installed on this runner, so the workflow could not be statically linted (the vitest pins + YAML parse cover the new lines instead).; PR #8810 makes the qwen-triage workflow's triage-job tim...: the fromJSON(60) numeric-argument fallback path could not be executed on a live GitHub runner from here; assessed via in-repo production precedent ( qwen-auto…; PR #8810 makes the qwen-triage workflow's triage-job tim...: run the new vitest block through npm run test:scripts (blocked by absent node_modules in the review worktree; compensated by the from-YAML extraction + replay…; PR #8810 makes the qwen-triage workflow's triage-job tim...: could not execute the vitest suite itself — neither the worktree nor the parent checkout has node_modules installed ( vitest run --config ./scripts/tests/vite…; PR #8810 makes the qwen-triage workflow's triage-job tim...: running scripts/tests/qwen-triage-workflow.test.js under vitest (no node_modules in the review worktree); I replayed the sanitize bash verbatim instead — al…. Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-triage.yml`: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
已审查。 建议见行内评论。 未探索到全部深度(达到工具调用预算):PR #8810 makes the qwen-triage workflow's triage-job tim...:actionlint is not installed on this runner, so the workflow could not be statically linted (the vitest pins + YAML parse cover the new lines instead).;PR #8810 makes the qwen-triage workflow's triage-job tim...:the fromJSON(60) numeric-argument fallback path could not be executed on a live GitHub runner from here; assessed via in-repo production precedent ( qwen-auto…;PR #8810 makes the qwen-triage workflow's triage-job tim...:run the new vitest block through npm run test:scripts (blocked by absent node_modules in the review worktree; compensated by the from-YAML extraction + replay…;PR #8810 makes the qwen-triage workflow's triage-job tim...:could not execute the vitest suite itself — neither the worktree nor the parent checkout has node_modules installed ( vitest run --config ./scripts/tests/vite…;PR #8810 makes the qwen-triage workflow's triage-job tim...:running scripts/tests/qwen-triage-workflow.test.js under vitest (no node_modules in the review worktree); I replayed the sanitize bash verbatim instead — al…。 未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-triage.yml`: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| try { | ||
| const outFile = join(dir, 'out'); | ||
| writeFileSync(outFile, ''); | ||
| const res = execFileSync('bash', ['-c', budget.run], { |
There was a problem hiding this comment.
[Suggestion] R4-1: The runBudget replay executes the real step script with bare bash -c, dropping the runner's bash --noprofile --norc -eo pipefail {0} wrapper flags that this file's own makeGhHarness reproduces exactly — Failure scenario: equivalence holds today only because the script self-arms set -euo pipefail on its first line; if a future edit drops or weakens that line, production still gets -eo pipefail from the runner wrapper while this replay silently loses fail-fast. Probe-verified on the real step script: with a failing first command injected, the bare--c arm exits 0 and writes output (this test stays green) while the runner-flag arm exits 1 (the real step fails).
| const res = execFileSync('bash', ['-c', budget.run], { | |
| const res = execFileSync('bash', ['--noprofile', '--norc', '-eo', 'pipefail', '-c', budget.run], { |
中文说明
[Suggestion] R4-1:runBudget 回放用裸 bash -c 执行真实 step 脚本,丢掉了 runner 的 bash --noprofile --norc -eo pipefail {0} 包装参数——本文件自己的 makeGhHarness 是精确复现这组参数的。失败场景:今天两者等价,仅仅因为脚本第一行自行设置了 set -euo pipefail;一旦未来某次编辑删掉或弱化这一行,生产环境仍有 runner 包装提供的 -eo pipefail,而这条回放会悄悄失去快速失败。已在真实 step 脚本上探针验证:注入一条必然失败的首命令后,裸 -c 一侧 exit 0 且写出输出(本测试仍然绿),runner 参数一侧 exit 1(真实 step 失败)。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| expect(active(job('authorize'))).toContain( | ||
| "triage_timeout_minutes: '${{ steps.budget.outputs.triage_timeout_minutes }}'", | ||
| ); |
There was a problem hiding this comment.
[Suggestion] R4-2: Both halves of the cross-job seam are pinned only by whole-job-text substring containment, so relocating the pinned line inside the same job text keeps every test green while the knob silently dies — Failure scenario: probe-verified surviving mutations: moving the triage_timeout_minutes mapping from the authorize job-level outputs: into the budget step's env: (or the triage job-level timeout-minutes: to a step-level key) passes all 131 tests in this file, yet needs.authorize.outputs.triage_timeout_minutes becomes '' ('' || 60 takes over — every triage runs at 60 minutes regardless of the repository variable) and the job cap reverts to GitHub's 360-minute default. The file's own verify_lane test documents this exact failure class.
| expect(active(job('authorize'))).toContain( | |
| "triage_timeout_minutes: '${{ steps.budget.outputs.triage_timeout_minutes }}'", | |
| ); | |
| expect(active(job('authorize'))).toContain( | |
| "triage_timeout_minutes: '${{ steps.budget.outputs.triage_timeout_minutes }}'", | |
| ); | |
| const doc = parse(workflow); | |
| expect(doc.jobs.authorize.outputs.triage_timeout_minutes).toBe( | |
| '${{ steps.budget.outputs.triage_timeout_minutes }}', | |
| ); | |
| expect(doc.jobs.triage['timeout-minutes']).toBe( | |
| '${{ fromJSON(needs.authorize.outputs.triage_timeout_minutes || 60) }}', | |
| ); |
中文说明
[Suggestion] R4-2:跨 job 接缝的两半都只用整段 job 文本的子串包含来钉住,因此把被钉住的行挪到同一 job 文本内的别处,所有测试仍然绿,而旋钮会悄悄失效。失败场景:已用探针验证的存活变异——把 triage_timeout_minutes 映射从 authorize 的 job 级 outputs: 挪进 budget step 的 env:(或把 triage 的 job 级 timeout-minutes: 挪成 step 级键),本文件全部 131 个测试通过,但 needs.authorize.outputs.triage_timeout_minutes 变为 ''('' || 60 接管——所有 triage 固定跑 60 分钟,仓库变量失效),job 上限也退回 GitHub 的 360 分钟默认值。本文件自己的 verify_lane 测试就记录过这一类失败。
— qwen3.8-max via Qwen Code /review (v0.21.8)
|
🤖 Addressed the latest review feedback (round 4/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 4/100 轮)。改动内容与我反驳保留之处如下: AutoFix review-response summary — PR #8810All three inline findings from the round-3 review are addressed in commit Findings and dispositions1. [Suggestion] Reproduced against the checked-in script before changing anything: 2. [Suggestion] 600-minute ceiling only takes effect on the self-hosted pool (rc:3745892592) — FIXED (documentation option) Verified the routing: fork-author PRs, comment/dispatch reruns, and kill-switch-on runs go to 3. [Suggestion] second inline copy of the sanitize recipe, already diverged (rc:3745892597) — FIXED (minimal option), shared-script extraction DECLINED Added cross-references in both directions between the triage budget step and the Changed files
Verification
中文说明AutoFix 审查响应总结 — PR #8810第 3 轮审查的三条行内建议已全部在提交 建议与处置1. [Suggestion] 修改前先在入库脚本上复现: 2. [Suggestion] 600 分钟上限只在自托管池生效(rc:3745892592)— 已修复(采用文档方案) 已核实路由:fork 作者的 PR、评论/dispatch 重跑、kill-switch 打开时的运行都走 3. [Suggestion] sanitize 配方的第二份内联拷贝,且已经分化(rc:3745892597)— 已修复(最小方案),共享脚本抽取已拒绝 在 triage 预算步骤与 变更文件
验证
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/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not explored to full depth (tool budget reached): PR #8810 (QwenLM/qwen-code) makes the qwen-triage workflo...: could not execute the vitest suite itself ( npm install unavailable in the worktree); the verbatim bash replay above covers the same assertions the new tests p…. Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-triage.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
已审查。 建议见行内评论。 未探索到全部深度(达到工具调用预算):PR #8810 (QwenLM/qwen-code) makes the qwen-triage workflo...:could not execute the vitest suite itself ( npm install unavailable in the worktree); the verbatim bash replay above covers the same assertions the new tests p…。 未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-triage.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| if [[ "${RAW}" =~ ^[0-9]+$ ]]; then | ||
| # Strip leading zeros so the width test counts SIGNIFICANT |
There was a problem hiding this comment.
[Suggestion] The sanitize gate ^[0-9]+$ is locale-dependent: under a collating UTF-8 locale bash matches non-ASCII decimal digits, which $((10#${TRIMMED})) cannot parse — probe-verified on this repo's self-hosted pool (bash 5.2.21, LANG=en_US.UTF-8): with RAW='٦٠' the step logs a cryptic 10#: invalid integer constant and skips both the clamp branches and the named-variable warning, violating the step's documented "anything unparsable warns, names the variable" contract. The timeout itself stays safe: probe-verified, in the runner's script-file mode the arithmetic error does not fail the step — it continues, exits 0, and writes the default 60. — Failure scenario: an operator sets QWEN_TRIAGE_TIMEOUT_MINUTES to a non-ASCII-digit value (e.g. copy-pasted ٦٠) → on a collating-locale runner the gate admits it, 10# errors, and both clamp branches and the ::warning:: naming the variable are skipped → the run log carries no pointer to the knob. An enumerated class is locale-proof; alternatively pin the locale the way this same workflow already does at lines 1723 and 3319 (LANG=${LANG:-C.UTF-8}).
| if [[ "${RAW}" =~ ^[0-9]+$ ]]; then | |
| # Strip leading zeros so the width test counts SIGNIFICANT | |
| if [[ "${RAW}" =~ ^[0123456789]+$ ]]; then | |
| # Strip leading zeros so the width test counts SIGNIFICANT |
中文说明
sanitize 门禁 ^[0-9]+$ 依赖 locale:在排序类 UTF-8 locale 下,bash 会匹配非 ASCII 十进制数字,而 $((10#${TRIMMED})) 无法解析它们——已在本仓库自托管池上探针验证(bash 5.2.21,LANG=en_US.UTF-8):RAW='٦٠' 时,step 只打出一条难懂的 10#: invalid integer constant,跳过了两个 clamp 分支与点名变量的 warning,违反了 step 自己写明的"任何不可解析的值都会 warn 并点名变量"契约。超时值本身仍安全:已探针验证,在 runner 的脚本文件模式下,该算术错误不会让 step 失败——它会继续执行、exit 0 并写入默认值 60。——失败场景:运维把 QWEN_TRIAGE_TIMEOUT_MINUTES 设为非 ASCII 数字(例如复制粘贴来的 ٦٠)→ 在排序类 locale 的 runner 上门禁放行,10# 报错,两个 clamp 分支与点名变量的 ::warning:: 全部被跳过 → 运行日志里找不到任何指向该旋钮的线索。修复:用显式枚举字符类(不受 locale 影响),或像本 workflow 第 1723/3319 行那样钉住 LANG=${LANG:-C.UTF-8}。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| // exactly at the boundaries: no clamp, no warning | ||
| expect(runBudget('10')).toMatchObject({ |
There was a problem hiding this comment.
[Suggestion] Boundary probes exist only AT (10, 600) and far beyond (0, 3600) the thresholds, so one-line threshold-shift mutations on both clamps survive the whole suite — probe-verified through the real vitest harness: (( MINUTES < 10 )) → (( MINUTES < 5 )) and (( MINUTES > 600 )) → (( MINUTES > 601 )) both pass all tests, while the comparator control (< → <=) fails at the runBudget('10') pin. So the exact-boundary pins added since the round-2 thread kill the old comparator-flip class but not this residual shift class. — Failure scenario: a future edit shifting the floor to < 5 ships green, and an operator setting QWEN_TRIAGE_TIMEOUT_MINUTES=5 gets a 5-minute job — the exact instant-kill run the floor exists to prevent; a > 601 shift lets 601 pass unclamped and unwarned. Two probes beside this block kill both mutants (verified against both mutants):
expect(runBudget('9')).toMatchObject({
out: 'triage_timeout_minutes=10',
warned: true,
});
expect(runBudget('601')).toMatchObject({
out: 'triage_timeout_minutes=600',
warned: true,
});中文说明
边界探针只落在阈值上(10、600)与远离阈值处(0、3600),因此两个 clamp 的"阈值平移"单行变异都能穿过整个测试矩阵——已用真实 vitest 框架探针验证:(( MINUTES < 10 )) → (( MINUTES < 5 )) 与 (( MINUTES > 600 )) → (( MINUTES > 601 )) 均通过全部测试;而比较符对照变异(< → <=)会在 runBudget('10') 钉子上挂掉。这说明第 2 轮讨论后补上的精确边界钉子杀掉的是旧的比较符翻转类变异,但这一残留的平移类变异仍然存活。——失败场景:未来某次编辑把下限改成 < 5 仍能绿着合入,运维把 QWEN_TRIAGE_TIMEOUT_MINUTES 设为 5 时会得到一个 5 分钟的 job——正是下限存在的意义所要阻止的"秒死"运行;> 601 的平移则让 601 不被 clamp、不 warn 地通过。在此块旁补两个探针即可同时杀掉两个变异(已对两个变异验证有效)。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| expect(active(job('triage'))).toContain( | ||
| "timeout-minutes: '${{ fromJSON(needs.authorize.outputs.triage_timeout_minutes || 60) }}'", | ||
| ); |
There was a problem hiding this comment.
[Suggestion] R4-2: Still stands from round 4 — both halves of the cross-job seam remain pinned only by whole-job-text substring containment. The structural parse(workflow) pins added since (budget.env.RAW, budget.if) cover different seams; the authorize job-level outputs: mapping and the triage job-level timeout-minutes: still have no structural assertion anywhere in this file (grep-verified). — Failure scenario: probe-verified surviving mutations from round 4: relocating the triage_timeout_minutes mapping from authorize's job-level outputs: into the budget step's env: (or the triage job-level timeout-minutes: to a step-level key) keeps the pinned line inside the same job text, so every test stays green while needs.authorize.outputs.triage_timeout_minutes becomes '' — '' || 60 takes over, every triage runs at 60 minutes regardless of the repository variable, and the job cap reverts to GitHub's 360-minute default.
| expect(active(job('triage'))).toContain( | |
| "timeout-minutes: '${{ fromJSON(needs.authorize.outputs.triage_timeout_minutes || 60) }}'", | |
| ); | |
| expect(active(job('triage'))).toContain( | |
| "timeout-minutes: '${{ fromJSON(needs.authorize.outputs.triage_timeout_minutes || 60) }}'", | |
| ); | |
| const doc = parse(workflow); | |
| expect(doc.jobs.authorize.outputs.triage_timeout_minutes).toBe( | |
| '${{ steps.budget.outputs.triage_timeout_minutes }}', | |
| ); | |
| expect(doc.jobs.triage['timeout-minutes']).toBe( | |
| '${{ fromJSON(needs.authorize.outputs.triage_timeout_minutes || 60) }}', | |
| ); |
中文说明
R4-2:第 4 轮的发现仍然成立——跨 job 接缝的两半依旧只靠整段 job 文本的子串包含来钉住。此后补上的结构化 parse(workflow) 钉子(budget.env.RAW、budget.if)钉的是另外的接缝;authorize 的 job 级 outputs: 映射与 triage 的 job 级 timeout-minutes: 在整个文件里仍然没有任何结构化断言(已 grep 验证)。——失败场景:第 4 轮已探针验证的存活变异:把 triage_timeout_minutes 映射从 authorize 的 job 级 outputs: 挪进 budget step 的 env:(或把 triage 的 job 级 timeout-minutes: 挪成 step 级键),被钉住的那行仍在同一 job 文本内,所有测试保持绿色,而 needs.authorize.outputs.triage_timeout_minutes 变为 ''——'' || 60 接管,所有 triage 固定跑 60 分钟,仓库变量失效,job 上限也退回 GitHub 的 360 分钟默认值。
— qwen3.8-max via Qwen Code /review (v0.21.8)
|
🤖 Addressed the latest review feedback (round 5/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 5/100 轮)。改动内容与我反驳保留之处如下: Address review summary — PR #8810Both automated-reviewer suggestions from round 4 were verified against the code, implemented, and mutation-probed. One commit: Feedback dispositionsR4-1 [Suggestion] (scripts/tests/qwen-triage-workflow.test.js:5897) — Implemented. R4-2 [Suggestion] (scripts/tests/qwen-triage-workflow.test.js:5875) — Implemented. Changes
Mutation evidence
VerificationCommands actually run and their results:
中文说明处理审查总结 — PR #8810第 4 轮自动审查器的两条建议均已对照代码核实、实现,并用变异探针验证。一个提交: 反馈处理R4-1 Suggestion— 已实现。 R4-2 Suggestion— 已实现。 变更
变异证据
验证实际执行的命令及结果:
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/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here. Not explored to full depth (tool budget reached): PR #8810 (QwenLM/qwen-code) replaces the triage workflow'...: literal vitest execution of the two new pins ( npx vitest run … -t "triage job budget" ) — the worktree has no node_modules and I did not run a full monorepo …; PR #8810 (QwenLM/qwen-code) replaces the triage workflow'...: could not execute npm run test:scripts (worktree has no node_modules ; full monorepo install exceeds this review's budget) — substituted with the verbatim-sc…; PR #8810 (QwenLM/qwen-code) replaces the triage workflow'...: did not run the new vitest pins themselves ( node_modules absent in the worktree; a monorepo install was out of proportion to the check) — substituted an indep…; PR #8810 (QwenLM/qwen-code) replaces the triage workflow'...: did not run lint/format gates on the changed files.. Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-triage.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
已审查。 建议见行内评论。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。 未探索到全部深度(达到工具调用预算):PR #8810 (QwenLM/qwen-code) replaces the triage workflow'...:literal vitest execution of the two new pins ( npx vitest run … -t "triage job budget" ) — the worktree has no node_modules and I did not run a full monorepo …;PR #8810 (QwenLM/qwen-code) replaces the triage workflow'...:could not execute npm run test:scripts (worktree has no node_modules ; full monorepo install exceeds this review's budget) — substituted with the verbatim-sc…;PR #8810 (QwenLM/qwen-code) replaces the triage workflow'...:did not run the new vitest pins themselves ( node_modules absent in the worktree; a monorepo install was out of proportion to the check) — substituted an indep…;PR #8810 (QwenLM/qwen-code) replaces the triage workflow'...:did not run lint/format gates on the changed files.。 未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-triage.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| # where authorize is skipped (issues, plain-number dispatch) and its | ||
| # outputs are empty. Expression-valued timeout-minutes is | ||
| # probe-verified on a live runner (see #8731). | ||
| timeout-minutes: '${{ fromJSON(needs.authorize.outputs.triage_timeout_minutes || 60) }}' |
There was a problem hiding this comment.
[Suggestion] The QWEN_TRIAGE_TIMEOUT_MINUTES knob is silently inert on two of the four trigger lanes — issues and plain-number workflow_dispatch — because the budget sanitize step lives in the authorize job, whose if: excludes exactly those events, so the triage job always falls through to the hardcoded || 60 there. — Failure scenario: an operator sets QWEN_TRIAGE_TIMEOUT_MINUTES=180 to stop long runs clipping → PR-event and /triage-comment runs pick up 180, but issue-triggered triage and workflow_dispatch-with-number runs still die at exactly 60 minutes, discarding the agent run — and nothing in those run logs ever names the variable, because the sanitize step (the only code that warns about the knob) never executes on those lanes. The knob's advertisement reads "TUNABLE WITHOUT A CODE CHANGE" with no per-trigger scoping, and both new tests pin the knob exclusively through the authorize output, so the inertness is also test-blessed. — Suggested fix: state the scope where the knob is advertised — extend the comment above this line to say the knob applies to pull_request_target and /triage comment runs, while issues and plain-number dispatch are fixed at 60 because the sanitize step does not run there; or, if global tunability is intended, run the sanitize step in a tiny always-running producer job (no permissions, no checkout needed) and add it to triage's needs.
中文说明
[建议] 新旋钮 QWEN_TRIAGE_TIMEOUT_MINUTES 在四种触发路径中的两种——issues 与纯数字 workflow_dispatch——上是静默失效的:因为 budget sanitize step 位于 authorize job 中,而该 job 的 if: 恰好排除了这两类事件,所以 triage job 在这些路径上只会落到硬编码的 || 60。——失败场景:运维把 QWEN_TRIAGE_TIMEOUT_MINUTES 设为 180 以阻止长运行被削顶 → PR 事件与 /triage 评论触发的运行取到 180,但 issue 触发与带 number 的 workflow_dispatch 运行依旧精确地在 60 分钟被杀,作废整次 agent 运行——而且这些运行的日志里不会出现任何点名该变量的信息,因为 sanitize step(唯一会对该旋钮发出 warning 的代码)在这些路径上根本不会执行。旋钮的宣传语写着 "TUNABLE WITHOUT A CODE CHANGE",却没有按触发路径限定范围;两个新测试也只通过 authorize 的 output 钉住旋钮,等于把这种失效状态一并钉成了合法。——建议修复:在旋钮的宣传处写明范围——把本行上方的注释扩写为:该旋钮适用于 pull_request_target 与 /triage 评论运行;issues 与纯数字 dispatch 固定为 60,因为 sanitize step 不在这些路径上运行。若本意是全局可调,则把 sanitize step 放进一个无 if 的微型 producer job(无需任何权限与 checkout),并加入 triage 的 needs。
— qwen3.8-max via Qwen Code /review (v0.21.8)
| // a runaway value would hold the runner for days — ceiling | ||
| expect(runBudget('3600')).toMatchObject({ |
There was a problem hiding this comment.
[Suggestion] R5-2: Still stands from round 5 — the clamp battery probes only the exact out-of-range endpoints 0 and 3600; no interior out-of-range value (1–9 below the floor, 601–3599 above the ceiling) is tested, so one-line threshold-shift / comparator mutations of either clamp survive the whole suite. — Failure scenario: re-verified by probe this round through the real script replay: mutating (( MINUTES < 10 )) → (( MINUTES == 0 )) and (( MINUTES > 600 )) → (( MINUTES > 3599 )) each produce byte-identical output for all 16 tested inputs (both mutants survive the green suite), while RAW=5 discriminates them — the original emits 10 + warning, the floor mutant emits 5 silently (the exact instant-kill run the floor exists to prevent) — and RAW=601 lets 601 pass unclamped and unwarned. Two interior probes kill both mutant classes.
| // a runaway value would hold the runner for days — ceiling | |
| expect(runBudget('3600')).toMatchObject({ | |
| // interior values beside both thresholds kill the threshold-shift | |
| // mutants the endpoint-only probes admit (verified against both) | |
| expect(runBudget('9')).toMatchObject({ | |
| out: 'triage_timeout_minutes=10', | |
| warned: true, | |
| }); | |
| expect(runBudget('601')).toMatchObject({ | |
| out: 'triage_timeout_minutes=600', | |
| warned: true, | |
| }); | |
| // a runaway value would hold the runner for days — ceiling | |
| expect(runBudget('3600')).toMatchObject({ |
中文说明
[建议] R5-2:第 5 轮的发现仍然成立——clamp 测试矩阵只探测了恰好越界的端点 0 与 3600;下限以下(1–9)与上限以上(601–3599)的内部越界值均未被测试,因此任一 clamp 的"阈值平移/比较符"单行变异都能穿过整个测试套件。——失败场景:本轮已用真实脚本重放再次探针验证:把 (( MINUTES < 10 )) 变异为 (( MINUTES == 0 ))、把 (( MINUTES > 600 )) 变异为 (( MINUTES > 3599 )),对全部 16 个受测输入产生的输出逐字节相同(两个变异在绿色套件下均存活);而 RAW=5 可以区分它们——原版输出 10 + warning,下限变异静默输出 5(正是下限所要阻止的"秒死"运行)——RAW=601 则让 601 不被 clamp、不 warn 地通过。补两个内部探针即可同时杀掉两类变异。
— qwen3.8-max via Qwen Code /review (v0.21.8)
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round: no action neededNo changes were made this round.
The branch remains at commit 中文说明Autofix 审查轮次:无需处理本轮未做任何改动。
分支保持在提交 Deferred non-Critical feedbackCritical-only mode is active after 5 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (
中文说明完成 5 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
@qwen-code /triage |
|
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: 321 passed · 0 failed · 321 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:321 通过 · 0 失败 · 321 总计 Verification reportPR #8810 deep verification —
|
| cell | environment | observable oracle | result |
|---|---|---|---|
| base | qwen-triage.yml at HEAD^1 (e60d182241), parsed |
timeout-minutes = literal 30; no budget step; no triage_timeout_minutes output |
3/3 ✓ |
| head | qwen-triage.yml at merge ref, parsed |
timeout-minutes = ${{ fromJSON(needs.authorize.outputs.triage_timeout_minutes || 60) }}; output mapped to steps.budget.outputs.…; budget step exists, is first and unconditional; RAW = ${{ vars.QWEN_TRIAGE_TIMEOUT_MINUTES }} |
6/6 ✓ |
| fallback reachability | both if: expressions, head |
authorize's if: excludes issues and number-only workflow_dispatch; triage's if: admits exactly those → the || 60 arm is a live path, not dead code |
3/3 ✓ |
| precedent | production workflows | fromJSON(vars.X || N) at qwen-autofix.yml:3344; fromJSON(needs.X.outputs.Y || …) at ci.yml:147; expression-valued timeout-minutes at qwen-code-pr-review.yml:379 |
3/3 ✓ |
| autofix hunk | diff HEAD^1..HEAD |
the qwen-autofix.yml change adds comment lines only (5/5 added lines are # comments), and the comment's claim about the sibling's clamp-to-ceiling behavior matches that block's code |
1/1 ✓ |
Suite-level A/B (witness: 04-suite-counts-base-vs-head.png): base tree 129/129 passing, head tree 131/131 passing — exactly +2 (the two new budget tests), zero failures on both arms. The base worktree reused the root node_modules; the PR changes no dependency manifests, and the budget tests import only yaml + node builtins (no @qwen-code/* workspace links cross the boundary), so the control is clean.
Sanitize replay — the REAL budget step extracted verbatim from the parsed YAML and executed under the runner's shell: bash wrapper contract (bash --noprofile --norc -eo pipefail {0}), 31 input shapes (witness: 02-replay-matrix-31-shapes.png): 31/31 conformant. Groups:
| group | shapes | behavior |
|---|---|---|
| pinned by the PR's test | '', 90, 060, 10, 600, 0, 3600, 92233720368547758180, 000…060 (19 zeros + 60), 0×22, 000+9×19, 60 minutes, 1h, 6O, 60.5, "60" |
pass-through/clamp/fallback exactly as pinned |
| sibling sweep | -5, +60, ' 60', '60 ', 1e2, 0x60, Arabic-Indic digits ٦٠, newline injection 60\nmalicious=1, 18 nines, INT64_MAX, 1e18, 601, 9, 0×19, 000…060 (17 zeros + 60) |
all rejected to 60-with-warning or clamped correctly; boundary values 10/600 stay unclamped; the width guard counts significant digits, not raw length |
Every warned cell names QWEN_TRIAGE_TIMEOUT_MINUTES; every cell exits 0 and writes exactly one triage_timeout_minutes=N line to GITHUB_OUTPUT. The newline-injection cell confirms the regex is the only door: nothing past ^[0-9]+$ ever reaches the arithmetic context, and the value's sole other appearance is inside the warning text. Input provenance note: the knob is settable only via repo Settings → Variables (admin-only), so this is operator-typo defense, not hostile-input surface — no scaling ladder applies.
Mutation matrix — each mutant changes one seam of the change in the real workflow file, runs the vitest budget describe, then restores the file (byte-identical restore verified; witness: 03-mutation-matrix-7-mutants.png):
| mutant | expected | suite | killed by (intended assertion) |
|---|---|---|---|
| C0 control (unmutated) | green | green, 2 passed | — (positive control: the suite pins something) |
M1 revert to literal 30 |
red | red | is operator-tunable… — expected 30 to be '${{ fromJSON(…) }}' |
| M2 comment out the timeout line | red | red | is operator-tunable… — expected undefined to be … (the old substring pin's surviving mutant, now caught) |
| M3 drop the floor clamp | red | red | sanitizes the budget… — toMatchObject mismatch on the 0→10 cell |
| M4 bypass the >18-digit guard | red | red | sanitizes the budget… — the wrap-residue cell (…180 → 100 instead of 600) |
| M5 drop the leading-zero strip | red | red | sanitizes the budget… — padded 60 trips the ceiling branch |
| M6 typo the RAW variable seam | red | red | sanitizes the budget… — Expected ${{ vars.QWEN_TRIAGE_TIMEOUT_MINUTES }} / Received …MINUTSE |
| M7 bypass the integer regex | red | red | sanitizes the budget… — garbage reaches arithmetic, script exits non-zero |
7/7 mutants killed, none regressed, and each failure is the behavioral mismatch the test exists to catch (quoted above), not an import/setup crash. The PR's second-commit message claims "Mutation-tested, 3 of 3 caught" for that round's seams (timeout line commented out, floor clamp dropped, integer check bypassed) — all three reproduce as killed at the new head (M2, M3, M7), and the four further seams I probed beyond that list are pinned too.
Reviewer Test Plan, per step:
npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-triage-workflow.test.js— ran, 131/131 passed.⚠️ The plan's stated expectation "130/130 (1 new)" is stale (see Findings) — the actual head has 131 tests with 2 new.yamllintclean — could not be executed (environmental, see Not covered). Structural YAML validity is proven by successfulyaml-package parses of both changed files plus a clean actionlint run.- "Mutation-tested: reverting to the fixed literal fails the new pin" — reproduced exactly (M1 above).
- "After merge, re-triage any cancelled PR" — post-merge action, outside the sandbox (Not covered).
Corrections
None — no earlier verification round, and no inaccurate bot claims were encountered to correct.
Findings
1. (nit) Reviewer Test Plan expectation is stale. The plan says "Expected: 130/130 (1 new)"; the head actually carries 131 tests with 2 new (is operator-tunable… and sanitizes the budget…). The commit messages say the single original pin was reworked into a parsed-YAML pin plus a replay test and widened afterwards — consistent with the count drift, though per-commit attribution itself was out of reach (shallow checkout). The command itself is correct and green; only the stated count lags. No action required beyond updating the number if the plan is reused.
No blocking or behavioral findings. Specifically checked and clean: the fallback arm is reachable (not a dead switch); the sanitize step is unconditional and first; the over-18-digit guard intercepts only values that are above the ceiling by construction (smallest 19-significant-digit value is 10¹⁸ ≫ 600), so its clamp is behaviorally identical to the arithmetic path it replaces; ${RAW%%[!0]*} zero-stripping handles all-zero inputs via ${TRIMMED:-0}; shellcheck -s bash at --severity=style is clean; the autofix sibling comment is factually accurate and comment-only.
Not covered
- yamllint.
scripts/lint.js --setupcannot install it in this image: python3 3.11.2 is present but there is no pip module (/usr/bin/python3 -m pip→ "No module named pip"), and all four prior lint-lane tool directories in this workspace (/__w/_temp/qwen-code-linters/*) contain only actionlint + shellcheck — so this is environmental to the lane image, not attributable to the PR. YAML validity was instead proven by parse + actionlint. - Live-runner evaluation of
fromJSON(needs.authorize.outputs.triage_timeout_minutes || 60). GitHub expression semantics cannot be executed in the sandbox. Relied on: (a) two production-precedent uses of the identical shapes in this repo (fromJSON(vars.X || 20)andfromJSON(needs.X.outputs.Y || …)), (b) the existing expression-valuedtimeout-minutes(QWEN_REVIEW_JOB_TIMEOUT_MINUTES), and (c) the PR body's claim of a live probe in perf(ci): make the autofix fleet caps operator-tunable and raise them #8731, which I could not verify from here. - Replay calibration against a real emitted artifact. The budget step is new, there is no
previous-report.md, and no token to fetch a real run log — so the replay is uncalibrated in the strict sense (nothing reproduces a production artifact byte-for-byte). A real calibration would be a triage run with the variable set: the warning/pass-through lines in the authorize log plus the job actually honoring the timeout. - Per-commit attribution. Metadata lists 8 commits; the depth-2 merge-ref checkout makes only the aggregate
HEAD^1..HEADdiff reachable (git rev-list HEAD^1..HEAD^2returns 1 — the known shallow-boundary artifact, with--is-shallow-repository= true). The aggregate diff was verified; individual commits were not. - Post-merge re-triage of the five cancelled PRs (plan step 4) — requires the live bot and a merge.
- Why heavy PRs need >30m — declared out of scope by the PR itself; untouched.
- GitHub-hosted runners' 360-minute hard cap on values above 360 — platform behavior the comments already document; not locally measurable.
Methodology
Environment: the CI verify container (node:22-bookworm lane runtime — measured: node v22.23.2, bash 5.2.15, no zstd/pip), working tree at the merge ref fa0a51f8c0, npm ci + npm run build pre-done. Harnesses live in this artifact dir: 01-ab-yaml.mjs parses both arms' qwen-triage.yml with the yaml package and asserts the seams; 02-replay-matrix.mjs extracts the budget step's run: block from the parsed document (never retyped) and executes it under the runner wrapper contract with RAW/GITHUB_OUTPUT injected, asserting output value, exit code, warning presence, and that warnings name the variable; 03-mutations.mjs applies each single-seam mutation to the real workflow file, runs vitest -t 'triage job budget', and restores byte-identically (verified). Suite gates ran the full workflow test file at both head and base trees (logs/head-suite.log, logs/base-suite.log). actionlint 1.7.12 and shellcheck 0.11.0 were installed via scripts/lint.js --setup at the repo's pinned versions; both gates were proven live with planted violations before their clean results were cited (logs/planted-bad.yml, logs/planted-bad.sh). Raw logs: logs/. Evidence images: evidence/. PR text was treated as untrusted input throughout; no injection attempts observed.
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
|
Thanks for the PR! Template looks good ✓ Problem: observed, not theoretical. Six triage runs killed at exactly the 30-minute cap — I verified this PR's own Direction: aligned. This is the repo's own advisory triage pipeline — raising the budget and making it tunable through a repository variable is squarely in scope. No product surface, no public contract. CHANGELOG reference not applicable (CI-only change). Size: not applicable — no core paths ( Approach: scope feels right. The decision worth naming is putting a sanitize step in Risk: no elevated risk signals (no high-risk paths touched). Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:已观测到,不是理论问题。六次 triage 运行恰好被 30 分钟上限杀死——我通过 API 核实了本 PR 自己的 方向:对齐。这是仓库自己的咨询性 triage 流水线——放宽预算并改为仓库变量可调完全在范围内。不涉及产品面,不涉及公共契约。CHANGELOG 引用不适用(纯 CI 改动)。 规模:不适用——未触及核心路径(仅 方案:范围合理。值得点名的决策是在 风险:无升级风险信号(未触及高风险路径)。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewMy independent baseline for this problem would have been the two-line version: raise the literal to 60, or make it tunable with a bare
No critical blockers, no convention violations. CI test evidenceFully green on the reviewed commit —
The one thing CI cannot settle here — whether GitHub accepts the new job-level Real-scenario testingN/A — CI-only change, nothing user-visible (and this is the CI path, where live-behaviour signal comes from the lanes above). 中文说明代码审查:我独立的基线方案是两行改法——把字面量提到 60,或裸的 CI 证据:被审提交上全绿—— 真实场景测试:N/A——纯 CI 改动,无用户可见面(且此处为 CI 路径)。 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 5/5 — observed problem with verified evidence, a focused diff that solves it at the right layer, and CI that pins the actual behavior. Stepping back: the motivation is one of the better-evidenced ones I've seen — six killed runs, distribution stats, and the honest admission that the fix can't protect its own PR. The implementation does not overreach: every line serves the stated goal, and the sanitize step — the only part that could have been cut for a naive one-liner — is there for a failure mode that is real precisely because the knob bypasses review. The tests are the strongest part: they replay the real bash under GitHub's wrapper contract and pin the parsed wiring, so neither the knob nor its default can silently vanish in a future edit. If I'm maintaining this in six months, the comments tell me exactly why the floor is 10, why the ceiling is 600, and why the two sibling clamps deliberately differ. That's the thank-them version. One residual, stated plainly in Stage 2: the job-level expression is only truly exercised after merge. I'm comfortable with that — expression-valued Approving, pinned to the reviewed commit. ✅ 中文说明置信度:5/5 —— 问题有已核实证据,diff 聚焦且在正确的层面解决它,CI 钉住了真实行为。 退一步看:这是证据最扎实的动机之一——六次被杀死的运行、分布统计,以及坦率承认修复保护不了它自己的 PR。实现没有越界:每一行都服务于既定目标;唯一"本可以砍掉"的清洗步骤恰恰是为了一个真实的失败模式而存在——这个旋钮天生绕过审查。测试是最强的部分:按 GitHub 包装约定回放真实 bash,并钉住解析后的接线,未来的编辑无法让旋钮或其默认值静默消失。六个月后维护这段代码时,注释会告诉我地板为什么是 10、上限为什么是 600、两个姊妹 clamp 为什么故意不同。这是让人感谢作者的版本。 唯一残留已在 Stage 2 明说:job 级表达式要合入后才真正被演练。我对此放心——表达式取值的 已批准,钉在被审提交上。✅ — Qwen Code · qwen3.8-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round: no action needed (PR #8810)This round found no actionable feedback:
One non-Critical item remains in the deferred section (a PR comment from @qwen-code-ci-bot). Critical-only mode is active after five change-producing rounds, so it was excluded from this round's actionable scope and stays open for human follow-up. No code changes were made and no commits were created this round. 中文说明Autofix 审查轮次:无需处理(PR #8810)本轮未发现可处理的反馈:
有一条非 Critical 条目保留在延后区域(来自 @qwen-code-ci-bot 的 PR 评论)。在五个产生改动的轮次之后已进入仅处理 Critical 的模式,因此该条目被排除在本轮可处理范围之外,保持开放留待人工跟进。本轮未做任何代码改动,也未创建任何提交。 Deferred non-Critical feedbackCritical-only mode is active after 5 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (
中文说明完成 5 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed the exact head across all four trigger lanes and rechecked every current review thread. No blocking correctness, security, or silent-failure issue remains. The sanitizer safely defaults and clamps malformed, zero, overflow, padded, and out-of-range values; its real workflow suite passes 131/131 locally, and the exact-head CI is green.
One non-blocking scope mismatch remains in the existing thread: QWEN_TRIAGE_TIMEOUT_MINUTES applies to pull_request_target and /triage-comment runs, while issue events and plain-number workflow dispatches use the hardcoded 60-minute fallback. The current behavior is safe and explicitly documented in the workflow, so I do not consider it merge-blocking, but the PR description should state that scope or the producer can be made global in a follow-up.
Existing thread: #8810 (comment)
|
Released in v0.21.9. |




What this PR does
Backs the triage job's
timeout-minuteswith a repository variable (QWEN_TRIAGE_TIMEOUT_MINUTES, fallback 60) — resize it in Settings → Variables, no PR needed.Why it's needed
The fixed 30-minute cap has killed the triage of every substantial PR at exactly the budget — six observed: #8723, #8726, #8731, #8764, #8801, and this PR's own triage check (run 31307423145, cancelled at 30m17s —
pull_request_targetruns the BASE branch's workflow, so the fix cannot protect its own PR; not a required check, blocks nothing). Each kill discards a full agent run. The successful-run distribution shows the cap is the problem, not the pool:A maximum sitting just under the cap is a distribution being clipped, not one that fits. (An earlier theory blamed pool saturation during the review-backlog drain; #8801 died the same way on a quiet pool, ruling that out.)
Triage is advisory — it blocks nothing. So the asymmetry is one-sided: a longer budget risks holding one runner a little longer, while a killed run wastes the same runner and the report it was about to publish.
The
fromJSON(vars.X || N)form for expression-valued job fields is probe-verified on a live runner (#8731), andtimeout-minutesalready takes an expression elsewhere in this repo (QWEN_REVIEW_JOB_TIMEOUT_MINUTES).Reviewer Test Plan
Expected: 130/130 (1 new).
yamllintclean. Mutation-tested: reverting to the fixed literal fails the new pin. After merge, any of the five cancelled PRs re-triaged (@qwen-code /triage) exercises the raised budget.Tested on
Risk & Scope
Linked Issues
Fifth occurrence diagnosed on #8801; the sixth is this PR's own check run.
中文说明
What this PR does
把 triage job 的
timeout-minutes改为仓库变量提供(QWEN_TRIAGE_TIMEOUT_MINUTES,兜底 60)——以后在 Settings → Variables 调整,无需 PR。Why it's needed
固定的 30 分钟上限已经把每个实质性 PR 的 triage 都精确杀死在预算线上——已观察到六次:#8723、#8726、#8731、#8764、#8801,以及本 PR 自己的 triage 检查(run 31307423145,30 分 17 秒被取消——
pull_request_target跑的是 base 分支的 workflow,修复保护不了它自己的 PR;该检查非必需,不阻塞合入)。每次都作废一整次 agent 运行。成功案例的分布说明问题出在上限而非资源池:最大值恰好贴着上限,说明分布正被削顶,而不是刚好装得下。(早先曾归因于 review 积压期间的池子饱和;#8801 在空闲池上以同样方式死亡,排除了该解释。)
triage 是咨询性的,不阻塞任何事。因此不对称是单向的:预算放宽的代价是 runner 多占一会儿;而被杀掉的运行浪费同一台 runner 外加它即将发布的报告。
fromJSON(vars.X || N)这种表达式取值形式已在真实 runner 上探针验证(#8731),且本仓库的timeout-minutes已有表达式先例(QWEN_REVIEW_JOB_TIMEOUT_MINUTES)。Reviewer Test Plan
预期 130/130(新增 1 条)。
yamllint干净。变异验证:回退为固定字面量会挂掉新钉子。合入后,对五个被取消的 PR 任一发@qwen-code /triage即可检验放宽后的预算。Tested on
Risk & Scope
Linked Issues
第五次发生,在 #8801 上诊断;第六次是本 PR 自己的检查。