Skip to content

fix(ci): give the ECS runner resolve wait a 60-minute budget - #10921

Open
yiliang114 wants to merge 17 commits into
mainfrom
fix/ecs-runner-resolve-budget
Open

fix(ci): give the ECS runner resolve wait a 60-minute budget#10921
yiliang114 wants to merge 17 commits into
mainfrom
fix/ecs-runner-resolve-budget

Conversation

@yiliang114

@yiliang114 yiliang114 commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Raises the Update ECS Runner Qwen workflow's registry wait from 25 to 90 minutes (RESOLVE_TIMEOUT_SECONDS 1500 → 5400) and the resolve job timeout from 30 to 100 minutes to cover it. Nothing else changes.

Why it's needed

release.yml dispatches this workflow the moment npm publish returns, but npm processes publishes asynchronously. The gap grows with package size and has now been measured twice: v0.22.3 took ~16 minutes (the data point the old 1500s budget was sized on), and v0.23.0 — a ~100 MB main package — took 55m22s end to end (npm accepted the upload 10:58:30Z; the registry served the version 11:53:52Z). The dispatched run 33747588713 gave up at the 25-minute mark and failed with the version still in flight, so every ECS pool stayed on the previous CLI version until a manual re-trigger. The new budget reserves ~1 hour for a main-package release and treats anything past 90 minutes as anomalous; the 30s poll means fast publishes still resolve immediately.

Note: qwen-code-core and qwen-live did not ship 0.23.0 in this release because they version independently — that is expected, not a stuck validation.

Reviewer Test Plan

How to verify

npx vitest run --config scripts/tests/vitest.config.ts scripts/tests/update-ecs-runner-qwen-workflow.test.js — 21/21 pass (the suite simulates the wait loop with small budgets; no test pins the production value). The real end-to-end check is the next release's npm-published dispatch: the resolve step should log the 404 retries and resolve once the version lands, instead of failing at 25 minutes.

Evidence (Before & After)

Before: run 33747588713 (v0.23.0 dispatch) — @qwen-code/qwen-code@0.23.0 is not on the registry yet; retrying in 30s. from 11:03Z until No published qwen version matches '0.23.0' after 1500s., job failed, fleet left on the old version.
After: manual re-trigger 33752983992 (version=0.23.0, registry already propagated) resolved and installed on the pools — confirming the failure mode was purely the wait budget.

Tested on

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

Workflow YAML parses cleanly and the workflow test suite passes locally.

Environment (optional)

N/A (workflow timing change).

Risk & Scope

  • Main risk or tradeoff: a genuinely lost publish now surfaces as a failure after 90 minutes instead of 25 — acceptable given a measured 55-minute propagation, and the error message names the version.
  • Not validated / out of scope: no change to the install/verify steps or the pool matrix.
  • Breaking changes / migration notes: none.

Linked Issues

Follow-up to the v0.23.0 release (run 33747588713 failure).

中文说明

本 PR 把 Update ECS Runner Qwen 工作流的注册表等待从 25 分钟提高到 90 分钟(RESOLVE_TIMEOUT_SECONDS 1500 → 5400),并把 resolve job 超时从 30 分钟提高到 100 分钟以匹配,其他不变。

原因:release.ymlnpm publish 返回后立刻 dispatch 本工作流,但 npm 异步处理发布,间隔随包体积增长。两次实测:v0.22.3 约 16 分钟(旧预算 1500 秒的来源);v0.23.0 约 100 MB 主包,端到端 55 分 22 秒(10:58:30Z npm 接收上传,11:53:52Z registry 正式可解析)。dispatch 触发的 run 33747588713 在 25 分钟处放弃并失败,机群全部停在旧版,靠人工重跑才恢复。新预算按主包发布预留约 1 小时,超过 90 分钟按异常处理;30 秒轮询不变,发布快时照样秒级解析。

说明:qwen-code-coreqwen-live 本次未发 0.23.0 是因为它们独立版本管理,不属于本次发布范围,不是卡在验证中。

验证:工作流测试套件 21/21 通过(用小预算模拟等待循环,不钉生产值);端到端看下次发布的 npm-published dispatch。证据:修复前 run 33747588713 在 25 分钟处失败、机群停在旧版;人工重跑 33752983992(注册表已传播)正常解析安装,证明失败纯粹是等待预算不足。风险:真正丢失的发布现在 90 分钟后才报错——鉴于实测 55 分钟的传播耗时,可接受。无破坏性变更。

The npm-published dispatch raced the registry again for v0.23.0: the
package took ~40 minutes to become resolvable (published 11:03Z,
visible ~11:43Z), but the resolve wait gave up at 25 minutes
(RESOLVE_TIMEOUT_SECONDS=1500) and run 33747588713 failed with the
version still in flight, leaving every pool on the previous version.
Raise the wait to 3600s and the job timeout to 70 minutes to match;
v0.22.3's 16-minute gap was the optimistic data point the old budget
was sized on.

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

qwen-code-ci-bot commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

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

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

yiliang114 and others added 3 commits September 3, 2026 20:09
Measured end to end for v0.23.0: npm accepted the upload at 10:58:30Z
and the registry served the version at 11:53:52Z — 55m22s for the
~100 MB main package, longer than the 60-minute budget the previous
commit carried. Raise the wait to 90 minutes (the anomaly threshold)
and the job timeout to 100 minutes; the 30s poll keeps fast publishes
resolving immediately.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
hk-4 and hk-5 installed 0.23.0 successfully ("changed 16 packages")
but `qwen --version` still reports 0.22.3 from /usr/local/bin/qwen,
which suggests the sudo npm install lands in a different prefix than
the one the pool resolves. Print the binary's readlink, the candidate
paths, sudo npm's global prefix, and the installed package.json before
failing so the next run identifies the stale copy instead of guessing.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Diagnostics on hk-4/hk-5 showed root's npm global prefix there is
/usr/local/lib/nodejs/node-v22.23.2-linux-x64, so `sudo npm install -g`
updated that tree while the pools kept resolving the stale
/usr/local/bin/qwen -> /usr/local/lib/node_modules copy (still 0.22.3).
Install with --prefix /usr/local so every pool updates the copy the
runner service resolves.

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

qwen-code-ci-bot commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Re-run at head 9b3b6643. The PR has moved 9 commits since the last triage pass (pinned at c64936d6), so this replaces that assessment.

The evidence here is still unusually good, and the newest commit closes the Critical that was standing against it. One new blocker appeared on the way — a CI gate this commit crossed, in Stage 2.

Template looks good ✓

Problem: observed, and I re-verified it independently rather than taking the description's word for it. Run 33747588713 attempt 1 (the v0.23.0 npm-published dispatch) has Resolve version failing 11:03:22Z → 11:28:44Z — 25m22s, exactly the old RESOLVE_TIMEOUT_SECONDS: '1500' budget — with all five pool legs skipped and Report a stale fleet firing. Worth knowing: that run's current job list reads all-green at 12:55Z because it was later re-run, so the failure only shows in the attempt-1 history. That is where I read it.

Direction: aligned. Keeping the ECS review fleet on the released CLI is this workflow's entire purpose, and every commit answers a failure that has logs behind it. It touches release infrastructure, but the author is a maintainer with admin on this repo and the direction is unambiguous, so I am not escalating.

Size: not applicable to the core gate — no packages/** paths. For reference the split is 98 production lines (.github/workflows/update-ecs-runner-qwen.yml, +88/−10) and 426 test lines (scripts/tests/update-ecs-runner-qwen-workflow.test.js, +424/−2). The production line count is not the issue; the byte count is, and that is the one thing blocking this PR — see Stage 2.

Approach: the budget half is exactly what I would have written from the title alone, and a test now pins the job-timeout-above-wait invariant so a bad merge cannot silently undo it. The install half has grown well past the stated goal: the body still says "Nothing else changes" and "no change to the install/verify steps", while the diff reworks the install step into a three-mode fallback and adds a verify diagnostics block — and the title still says "60-minute budget" where the diff ships 90 minutes (5400). I am not asking for a split. This is round 7, the install work traces to real fleet failures surfaced by the same dispatch loop, and AGENTS.md is explicit about not letting review rounds balloon a PR. But please sync the title and body before the squash, so the commit message that lands on main describes what actually landed.

Risk: no elevated risk signals — no high-risk paths matched.

Moving on to code review. 🔍

中文说明

本次在 head 9b3b6643 重跑。距上次 triage(固定在 c64936d6)已推进 9 个提交,因此本条替代此前的判断。

这里的证据依然非常扎实,最新提交也解决了此前挂着的 Critical。但过程中出现了一个新的阻断项——本提交刚刚越过的一条 CI 门禁,详见 Stage 2。

模板完整 ✓

问题:已观测到的真实问题,且我独立复核过,没有直接采信描述。run 33747588713 的第 1 次尝试(v0.23.0 的 npm-published dispatch)中 Resolve version 于 11:03:22Z → 11:28:44Z 失败——25 分 22 秒,正好耗尽旧的 RESOLVE_TIMEOUT_SECONDS: '1500' 预算——五个池的 leg 全部 skipped,并触发了 Report a stale fleet。需要知道的是:该 run 当前的 job 列表在 12:55Z 显示全绿,因为它后来被重跑过,失败只存在于第 1 次尝试的历史记录里。我就是在那里读到的。

方向:对齐。让 ECS 评审机群保持已发布的 CLI 版本,正是这个工作流的全部目的;每个提交都对应一个有日志支撑的失败。它触及发布基础设施,但作者是本仓库的维护者(admin 权限),方向明确,因此不做升级。

规模:核心门禁不适用——未触及 packages/** 路径。供参考的拆分是 98 行生产代码.github/workflows/update-ecs-runner-qwen.yml,+88/−10)与 426 行测试scripts/tests/update-ecs-runner-qwen-workflow.test.js,+424/−2)。生产行数不是问题;字节数才是,而这正是本 PR 唯一的阻断项——见 Stage 2。

方案:预算这一半与我只看标题会写出的方案完全一致,而且现在有测试钉住"job 超时高于等待预算"这一不变式,坏的 merge 无法悄悄把它改掉。安装这一半已明显超出既定目标:正文仍写着"其他不变"和"不改 install/verify 步骤",而 diff 实际把 install 步骤重构成三模式回退,并新增了 verify 诊断块——标题也仍写"60 分钟",而 diff 实际是 90 分钟(5400)。我要求拆分:这已是第 7 轮,安装相关的改动都源自同一个 dispatch 循环中暴露的真实机群失败,且 AGENTS.md 明确要求不要让评审轮次把 PR 越滚越大。但请在 squash 前同步标题与正文,让落到 main 上的提交信息如实描述实际内容。

风险:无升级风险信号——未命中高风险路径。

进入代码审查 🔍

Qwen Code · qwen3.8-max-2026-09-02

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

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Code review

My independent proposal from the title and motivation alone: raise RESOLVE_TIMEOUT_SECONDS 1500 → 5400 to cover the measured 55m22s with headroom, lift the resolve job's timeout-minutes above the wait, keep the 30s poll, and touch nothing else. I also considered a smaller-footprint alternative — leave the budget alone and have the dispatch re-run on failure instead of holding one long job — but the bump matches the poll loop that already exists, so it is the genuinely minimal change.

The budget half of the diff is that proposal, exactly. 5400 against timeout-minutes: 100 leaves 10 minutes of headroom, the interval stays 30 so fast publishes still resolve immediately, and the deadline arithmetic and the held-back-stderr replay are untouched.

The Critical that was standing against this PR is closed. R1-2 prescribed three ways out and option (1) was "keep the pin for the pools that need it and add the authorized no---prefix shape as an ordered second sudo mode — after the pinned one, before the runner-user fallback". install_qwen_named_spec() plus the || chain does precisely that, and mode 2's argv is the one main already ran green on hk-1/hk-2 before this PR touched it. R1-2's first condition is met too: the command-specific stub is narrowed to the observed sudoers spec — it now case-matches the exact authorized argv rather than accepting anything containing npm install -g, which was the widening that let the previous suite pass a command line the real machines reject. The tests read the effective user npm ran as, assert exactly one install happened, and assert the absence of --prefix, so wedging the pin back into that argv turns them red. That is a real pin, not a restatement.

The verify step also holds up. actual="$(...)" || actual="(qwen --version failed, exit $?)" reports the right code — $? is still the failed assignment's status when the right arm expands — the capture is stdout-only so a runtime warning cannot fail a healthy install, every diagnostic is guarded (2>/dev/null, || true, and both cat | grep pipelines covered under pipefail), every sudo in the block is -n, and the closing test is unchanged. The block can add signal without ever changing the outcome.

Blocking: this commit crosses the workflow size ratchet and reds a required lane

Lint & Static (ubuntu-latest, Node 22.x) fails on this head, and the annotation names this PR's own file:

.github/workflows/update-ecs-runner-qwen.yml grew to 12618 bytes, 5431 over its recorded 7187 (allowance 4096). Move prose into a sibling .md and long steps into .github/scripts/ — or, if the growth is real, update .github/workflows/.size-baseline in this PR and say why.

I verified every number rather than trusting the annotation. .github/workflows/.size-baseline line 63 records 7187 update-ecs-runner-qwen.yml; the allowance in check-workflow-size.sh is 4096, so the threshold is 11283; the file fetched raw at this head SHA is 12618 bytes. This PR changes two files and .size-baseline is not one of them. The script's file_matches_base escape hatch does not apply here — it downgrades to a warning only for a byte-identical copy, and this PR genuinely edits the file, so it takes the hard-error branch. Merging this reds main.

Two things make this worth acting on now rather than shrugging at:

  • It is new at this exact commit. At the previous head 6aab6c577 the file was 11262 bytes — passing by 21 bytes. 9b3b6643 added ~1356 bytes and crossed the line. The size growth that /review round 6 recorded as a deferred Suggestion is now a hard failure.
  • It is not infra noise. I sampled 12 open PRs: six have Lint & Static green, and the one other red (ci: make the Prettier lane a real gate, and drop the release copy that verifies nothing #11117) carries no ratchet annotation at all — a different cause. This failure is unique to this PR.

Either remedy the gate names is fine. Moving the ~40 lines of install-mode prose into a sibling .md (or the install step into .github/scripts/) is the better one — the reasoning is genuinely valuable and a sibling doc keeps it without spending the ratchet. Bumping .size-baseline in this PR with a one-line reason is the faster one and is explicitly allowed.

Not blocking, recorded so it is not lost

The || chain cannot distinguish "sudo rejected this argv" from "npm failed while running under sudo" — both are the same non-zero exit. On hk-4/hk-5 a transient npm failure in mode 1 therefore falls through to mode 2, whose unpinned argv is authorized there and installs into root's custom Node prefix, which is the wrong tree and the exact thing the pin exists to prevent. It fails closed: Verify then mismatches, prints the diagnostics this PR added, and the test fails the leg, so the fleet is never silently stale and the stray install is visible. This is already in /review's round-6 deferred list, and given this is round 7 — where AGENTS.md says land only Critical fixes — I am not adding it as a blocker.

Testing evidence

Unattended CI run — this review executed no PR code, built nothing, and ran no test. Everything below is the PR's own CI on the reviewed commit, read through the API, plus the fleet dispatch history.

Check Conclusion
Lint & Static (ubuntu-latest, Node 22.x) ❌ failure
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ❌ failure
Test (ubuntu-latest, Node 22.x) 🚫 cancelled
review-pr ⏳ in_progress
Classify PR ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Integration Tests (no-AK, No Sandbox) ✅ success
Remind on force-push ✅ success
assign ✅ success
authorize ✅ success
delay-automatic-review ✅ success
label ✅ success
route ✅ success

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

Also skipped on this head: Test (macos-latest), Test (windows-latest), Integration Tests (CLI, No Sandbox) — merge_group-only lanes, so the bash-3.2/BSD and Windows paths were walked by reading, not executed.

Classifying the three red lanes, from the diff and the check identities rather than from anything in the log bodies:

On the author's earlier CI-attribution comment: it was correct, and I confirmed the substance of it — but it was written about head 6aab6c577, where Lint & Static was red for the main-side check:no-webui reason. On this head that infra cause is gone and a real one took its place, so the "new head should pick up main's corrected ci.yml" expectation did not hold. Two of the three lanes are still infra; the third is now this diff.

Sandboxed verification would settle two things, and they are different questions:

  • The fleet claim — that the three-mode fallback actually updates all five pools — can only be settled by a real workflow_dispatch of this workflow at head with all five legs green. That is the oracle R1-2 named as a merge precondition, and it is the one piece of evidence still missing: there is no dispatch of this workflow at any head after c64936d6f6 (2026-09-03T12:18Z). The last one, run 33757297024 at 16b7737318, left hk-1 and hk-2 failed with hk-3/4/5 green — and that head carried the probe-based shape, not mode 2. So the two pools mode 2 exists to save have never been observed green with it. Neither sandboxed lane can reach the production ECS pools; this one needs a dispatch.
  • @qwen-code /verify would settle the narrower, still-open question of whether the new replay tests actually pin the mode-selection logic: A/B the suite with mode 2 removed, and with --prefix /usr/local wedged back into install_qwen_named_spec. If the suite stays green under either mutation, the narrowed stub is not doing the work it claims to do.

Not verified: the npm propagation timestamps in the description (10:58:30Z / 11:53:52Z) — the author's measurement, which I cannot reproduce from the API and did not re-derive. The 25-minute exhaustion of the old budget I did verify independently, from the attempt-1 job timings.

中文说明

代码审查:我只看标题和动机时的独立方案是——把 RESOLVE_TIMEOUT_SECONDS 从 1500 提到 5400 以覆盖实测的 55 分 22 秒并留出余量,把 resolve job 的 timeout-minutes 提到等待之上,保留 30 秒轮询,其余不动。我也考虑过一个占用更小的替代方案:不动预算,改为让 dispatch 失败后重跑,而不是长时间占着一个 job——但调预算与现有的轮询循环天然契合,才是真正最小的改动。diff 的预算部分与该方案完全一致:5400timeout-minutes: 100,留 10 分钟余量,轮询间隔仍是 30 秒,截止时间的算术与被暂存的 stderr 重放逻辑都没动。

此前挂着的 Critical 已经解决。 R1-2 给了三条出路,其中第 (1) 条是"为需要 pin 的池保留 pin,并把 sudoers 授权的不含 --prefix 的 argv 作为有序的第二个 sudo 模式加入——排在 pinned 模式之后、runner-user 回退之前"。install_qwen_named_spec()|| 链正是这么做的,且模式 2 的 argv 就是本 PR 之前 main 在 hk-1/hk-2 上跑绿的那一条。R1-2 的第一个条件也满足了:command-specific 桩已收窄到实测的 sudoers 规格——现在按精确授权的 argv 做 case 匹配,而不再接受任何含 npm install -g 的命令行,正是那个放宽让此前的测试套件通过了一条真机拒绝的命令行。测试读取 npm 实际运行的用户身份,断言只发生了一次安装,并断言不含 --prefix,所以把 pin 塞回该 argv 会让测试变红。这是真正的钉子,不是复述。

verify 步骤同样站得住:|| actual= 分支能正确报告退出码(右分支展开时 $? 仍是那次失败赋值的状态),捕获只取 stdout 因此运行时告警不会让健康的安装失败,每条诊断都有保护(2>/dev/null|| true,两条 cat | grep 管道在 pipefail 下也被覆盖),块内每个 sudo 都是 -n,结尾的 test 未改。该块只增加信息,绝不改变结果。

阻断项:本提交越过了工作流体积棘轮,把必需检查搞红了。 Lint & Static (ubuntu-latest, Node 22.x) 在此 head 失败,注解点名的正是本 PR 自己的文件:.github/workflows/update-ecs-runner-qwen.yml 增长到 12618 字节,超出记录的 7187 共 5431 字节(允许 4096)。我没有采信注解,而是逐个核对了数字:.size-baseline 第 63 行记录 7187 update-ecs-runner-qwen.ymlcheck-workflow-size.sh 的允许量是 4096,阈值为 11283;在 head SHA 上取到的原始文件是 12618 字节。本 PR 只改两个文件,.size-baseline 不在其中。脚本的 file_matches_base 豁免在这里不适用——它只对逐字节相同的副本降级为警告,而本 PR 确实修改了该文件,因此走硬失败分支。合并会把 main 搞红。

有两点说明这事应当现在处理:一是它恰好在本提交才出现——上一个 head 6aab6c577 时文件是 11262 字节,仅以 21 字节之差通过,9b3b6643 增加了约 1356 字节越线,/review 第 6 轮记为延后 Suggestion 的体积增长如今成了硬失败;二是它不是基础设施噪声——我抽查了 12 个开放 PR,6 个 Lint & Static 是绿的,另一个红的(#11117)根本没有棘轮注解,属不同原因。此失败为本 PR 独有。

门禁给出的两种补救都可以:把约 40 行安装模式的说明移到同级 .md(或把安装步骤移到 .github/scripts/)是更好的一种——这些推理确实有价值,同级文档既能保留又不消耗棘轮额度;在本 PR 内更新 .size-baseline 并给一行理由是更快的一种,也是明确允许的。

非阻断,记录以免丢失|| 链无法区分"sudo 拒绝了该 argv"与"npm 在 sudo 下执行失败"——两者都是同一个非零退出。因此在 hk-4/hk-5 上,模式 1 的一次瞬时 npm 失败会落到模式 2,而模式 2 不含 pin 的 argv 在那里是被授权的,会装进 root 的自定义 Node 前缀,即错误的那棵树,也正是 pin 要防的事。它失败是收敛的:随后 Verify 不匹配、打印本 PR 新增的诊断、test 让该 leg 失败,所以机群不会被悄悄留在旧版,误装也是可见的。这一条已在 /review 第 6 轮的延后清单中;鉴于已是第 7 轮(AGENTS.md 要求只落 Critical 修复),我不将其列为阻断项。

测试证据:本次为无人值守 CI 运行——未执行任何 PR 代码、未构建、未跑测试。以下全部来自被审查提交上 PR 自己的 CI(经 API 读取)以及机群 dispatch 历史。三条红检查的归类依据是 diff 与检查身份,而非日志正文中的任何说法:Lint & Static 为 PR 造成(即上述阻断项,注解点名本 PR 文件,算术与仓库内基线吻合);Test (ubuntu-latest) 非 PR 造成(注解为 The job has exceeded the maximum execution time of 2h0m0s,job 从 14:06:30Z 跑到 16:06:34Z 正好两小时,本 PR 不含任何产品源码,且同一检查在 #11113#11107#11105#11103#11101 上同样红或被取消);web-shell E2E Smoke 非 PR 造成(在 #11107#11105#11101 上红或被取消,且本 diff 未触及 integration-tests/ 下任何内容)。关于作者此前的 CI 归因评论:它是正确的,我核实了其实质内容——但它写的是 head 6aab6c577,当时 Lint & Static 红的原因是 main 侧的 check:no-webui。在此 head 上那个基础设施原因已消失,取而代之的是一个真实原因,因此"新 head 应当拿到 main 修正后的 ci.yml"的预期并未成立。三条中有两条仍是基础设施问题,第三条现在属于本 diff。

沙箱验证可以解决两件事,且是两个不同的问题:机群层面的断言(三模式回退真的能更新全部五个池)只能由在 head 上真实 workflow_dispatch 本工作流、五条 leg 全绿来验证——这正是 R1-2 指定为合并前置条件的验证方式,也是目前唯一缺失的证据:在 c64936d6f6(2026-09-03T12:18Z)之后,本工作流没有任何 head 上的 dispatch。最近一次是 16b7737318 的 run 33757297024,hk-1 与 hk-2 失败、hk-3/4/5 绿——而那个 head 用的是探针式结构,不是模式 2。所以模式 2 要拯救的那两个池,从未被观测到用它跑绿过。两个沙箱通道都到不了生产 ECS 机群,这一项需要真实 dispatch。@qwen-code /verify 可以解决更窄、但仍未决的问题:新的重放测试是否真的钉住了模式选择逻辑——移除模式 2、以及把 --prefix /usr/local 塞回 install_qwen_named_spec,两种变异下分别 A/B 跑该套件;若两种情况下套件仍为绿,说明收窄后的桩并未起到它声称的作用。

未验证:描述中的 npm 传播时间戳(10:58:30Z / 11:53:52Z)——那是作者的测量,我无法从 API 复现,也未重新推导。旧预算 25 分钟耗尽这一点我通过第 1 次尝试的 job 时间独立核实。

Qwen Code · qwen3.8-max-2026-09-02

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

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 2/5 — the design is right and the hard Critical is genuinely closed, but as it stands this cannot merge: it reds a required lane on its own head, and the fleet evidence its own reviewer set as a merge precondition was never collected.

Stepping back over the whole arc. My independent proposal was two numeric constants, and the budget half of this PR is exactly that, done cleanly — 90 minutes against a measured 55m22s, a job timeout that keeps 10 minutes of headroom above the wait, the poll interval untouched so fast publishes still resolve immediately, and now a test pinning the timeout-above-wait invariant so a bad merge cannot quietly restore the original failure. I verified the motivation myself rather than accepting it: run 33747588713's attempt-1 Resolve version job ran 11:03:22Z → 11:28:44Z and failed, 25m22s, which is the old 1500s budget to the second. That is a real bug with a real timestamp, not a hypothesis.

The install half earned its place too. It came out of a dispatch loop against the live fleet, one observed failure per commit, and the newest commit does the right thing in the right way: R1-2 named three possible fixes and this takes option (1), adding the exact authorized argv as an ordered second sudo mode. It also closed the gap that made the earlier green suite worthless — the stub now models the observed sudoers spec instead of accepting anything containing npm install -g, and the tests assert which user npm actually ran as, so the pin is real. If I had to maintain this in six months, the comments explaining why mode 2 must stay after mode 1 would make me thank the author rather than curse them. That is not a small thing in a file nobody reads until the fleet goes stale.

So why not approve. Two reasons, and neither is a design objection.

The first is mechanical and it is a wall: this commit took the workflow file from 11262 bytes to 12618, crossing the repo's 11283-byte ratchet threshold by 1335 bytes, and .size-baseline was not bumped. Lint & Static is red on this head naming this PR's own file, and merging it reds main. The previous head passed that gate by 21 bytes, which is why nothing before this round caught it. This is the sort of failure that is trivial to fix and impossible to merge around, and it is worth saying plainly that it is not infra noise — I checked, because the author's earlier attribution comment reasonably argued that all three red lanes were main-side. Two of the three still are. This one is not.

The second is the missing oracle. R1-2 required an all-five-pools-green dispatch before merging the update-path change, and there has been no dispatch of this workflow at any head since c64936d6f6 on 09-03. The last one left hk-1 and hk-2 failed. Mode 2 exists solely to save those two pools, and it has never been observed doing it. The narrowed stub makes the suite able to see the failure class now, which is real progress — but a replay is still a model of the fleet, and the whole reason R1-2 asked for a dispatch is that the earlier model was wrong in exactly the way that mattered.

I am requesting changes rather than deferring, because both items have a clear owner and a clear next step, and neither needs a maintainer's judgment call. Fix the ratchet, run the dispatch, and this is ready — I would not be surprised to approve it on the next pass. The title/body mismatch (still "60-minute budget", still "Nothing else changes") is noted in Stage 1 as hygiene; per this repo's own guidance on not letting review rounds balloon a PR, I am deliberately not treating it, or the deferred ||-chain concern, as a reason to hold this back.

中文说明

置信度:2/5 —— 设计是对的,那个硬 Critical 也确实解决了,但当前状态无法合并:它在自己的 head 上把必需检查搞红了,而其自身评审设定的合并前置条件(机群证据)从未采集。

退一步看整个过程。我独立的方案是两个数字常量,而本 PR 的预算部分正是如此,且做得干净——针对实测 55 分 22 秒给出 90 分钟,job 超时在等待之上保留 10 分钟余量,轮询间隔未动因此发布快时仍会立即解析,并且现在有测试钉住"超时高于等待"这一不变式,坏的 merge 无法悄悄恢复原来的失败。动机我自己核实过,没有直接采信:run 33747588713 第 1 次尝试的 Resolve version job 从 11:03:22Z 跑到 11:28:44Z 后失败,25 分 22 秒,与旧的 1500 秒预算分毫不差。这是一个带真实时间戳的真实 bug,不是假设。

安装这一半也挣得了它的位置。它来自针对真实机群的 dispatch 循环,一个提交对应一个观测到的失败,而最新提交以正确的方式做了正确的事:R1-2 给出了三种可能的修法,本提交采纳第 (1) 种,把精确授权的 argv 作为有序的第二个 sudo 模式加入。它还补上了让此前绿色套件失去价值的那个缺口——桩现在建模实测的 sudoers 规格,而不再接受任何含 npm install -g 的命令行,且测试断言 npm 实际以哪个用户运行,所以这个钉子是真的。如果六个月后由我来维护,那些解释为什么模式 2 必须排在模式 1 之后的注释,会让我感谢作者而不是咒骂作者。在一个平时无人阅读、直到机群变旧才被打开的文件里,这不是小事。

那么为什么不批准。两个原因,都不是对设计的异议。

第一个是机械性的,且是一堵墙:本提交把工作流文件从 11262 字节推到 12618 字节,越过仓库 11283 字节的棘轮阈值 1335 字节,而 .size-baseline 没有更新。Lint & Static 在此 head 上是红的,点名的正是本 PR 自己的文件,合并会把 main 搞红。上一个 head 以 21 字节之差通过该门禁,这也是为什么此前各轮都没抓到。这类失败修起来极其简单、却绕不过去。也要明确说一句:它不是基础设施噪声——我核查过,因为作者此前的归因评论合理地主张三条红检查都来自 main 侧。其中两条现在仍然是。这一条不是。

第二个是缺失的验证标准。R1-2 要求在合并 update-path 改动前完成一次五池全绿的 dispatch,而自 09-03 的 c64936d6f6 之后,本工作流在任何 head 上都没有 dispatch 过。最近一次让 hk-1 与 hk-2 失败。模式 2 存在的唯一目的就是拯救这两个池,却从未被观测到做到过。收窄后的桩使套件现在能看见这一失败类别,这是实实在在的进步——但重放终究只是机群的模型,而 R1-2 之所以要求真实 dispatch,正因为此前的模型在最关键的地方是错的。

我选择请求修改而非转交,因为这两项都有明确的负责人和明确的下一步,都不需要维护者拍板。修掉棘轮、跑一次 dispatch,这个 PR 就可以了——下一轮我批准它也不会意外。标题/正文不一致(仍写"60 分钟"、仍写"其他不变")已在 Stage 1 作为规范性问题记录;按本仓库自身关于不要让评审轮次把 PR 越滚越大的指引,我刻意不把它、以及那条延后的 || 链问题,当作拦住本 PR 的理由。

Qwen Code · qwen3.8-max-2026-09-02

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

yiliang114 and others added 2 commits September 3, 2026 20:47
…issing

The hk-1/hk-2 pool machines reject sudo with "a password is required"
for the runner user, so the install failed after 3 attempts on two
consecutive runs. Probe passwordless sudo once (sudo -n true) and, when
absent, install as the runner user itself, which succeeds when that
user owns the /usr/local prefix.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
chiga0
chiga0 previously approved these changes Sep 3, 2026

@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.
Approval blockers: none.

Tier: Standard.

Note: ci-bot reviewed c64936d6; this review covers the current HEAD 3eeeaf89f5 (adds the sudo -n true fallback).

R1-1 — Major · Test validity (class 5) · supported

scripts/tests/update-ecs-runner-qwen-workflow.test.js:115 checks:

expect(workflow).toContain('sudo env -u NPM_CONFIG_PREFIX npm install -g');

Commit 16b773731890 refactored the install into an INSTALL array (line 113 of the diff):

INSTALL=(sudo env -u NPM_CONFIG_PREFIX)   # no longer followed by 'npm install -g' on the same line
...
"${INSTALL[@]}" npm install -g --prefix /usr/local ...

The literal string sudo env -u NPM_CONFIG_PREFIX npm install -g no longer appears in the workflow file. This test cannot pass against the submitted HEAD. The PR's "21/21 pass" was accurate for an earlier commit.

Production behaviour is correct (sudo path still passes -u NPM_CONFIG_PREFIX), but the test no longer guards it. Suggested fix:

expect(workflow).toContain('INSTALL=(sudo env -u NPM_CONFIG_PREFIX)');
expect(workflow).toContain('"${INSTALL[@]}" npm install -g');

R1-2 — Minor · Stated intent (class 10)

PR title says "60-minute budget"; the actual wait is 90 minutes (RESOLVE_TIMEOUT_SECONDS: 5400) and the job timeout is 100 minutes. PR body says "Nothing else changes" but the diff adds --prefix /usr/local, the sudo -n true fallback, and verify diagnostics. (ci-bot flagged this at c64936d6; still uncorrected.)

What else was checked

  • Wait loop: RESOLVE_TIMEOUT_SECONDS: 5400 and 30s poll consistent; deadline arithmetic unchanged.
  • timeout-minutes: 100 covers the 90-minute wait with 10-minute headroom.
  • sudo -n true fallback: Correct pattern for detecting passwordless sudo. Addresses the hk-2 sudo: a password is required failure ci-bot identified. Non-sudo path (env npm install -g --prefix /usr/local) correctly installs to /usr/local — npm CLI --prefix overrides NPM_CONFIG_PREFIX per npm config precedence.
  • sudo rm -rf cleanup: Unconditional sudo, silenced with || true. On no-sudo machines cleanup is skipped silently; acceptable for crash-cleanup.
  • Diagnostics block: Failure-path only, all commands guarded with || true/2>/dev/null. No effect on success path.
  • report_failure job: Unchanged and correct.
  • Cross-check vs ci-bot: ci-bot did not review the current HEAD. F1 is new (introduced by 16b773731890). F2 was already noted by ci-bot. hk-2 concern appears addressed; live dispatch is the final oracle.

Reviewed with AI assistance.

Comment thread .github/workflows/update-ecs-runner-qwen.yml Outdated
@yiliang114
yiliang114 enabled auto-merge September 3, 2026 15:24

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

Reviewed at head 3eeeaf89.

Blocking — this PR turns the required Test (ubuntu-latest) lane red on its own head, and merging it reds main. The commit 16b77373 refactor moved the install command into an INSTALL=(sudo env -u NPM_CONFIG_PREFIX) array with "${INSTALL[@]}" npm install -g on a separate line, so the pre-existing pin scripts/tests/update-ecs-runner-qwen-workflow.test.js:115 (toContain('sudo env -u NPM_CONFIG_PREFIX npm install -g')) no longer matches. The failing run on 3eeeaf89 is the exact assertion (annotation captured verbatim). chiga0's R1-1 thread (still unresolved) carries the fix — two toContain lines pinning the array form — and confirms production behaviour is correct: the sudo path still strips NPM_CONFIG_PREFIX, and on the no-sudo fallback the CLI --prefix /usr/local outranks the env var, so the fleet still lands in the right tree.

Expected, and cheap:

  1. Update scripts/tests/update-ecs-runner-qwen-workflow.test.js:115 to chiga0's suggested pair of assertions, and re-run cd scripts && npx vitest run tests/update-ecs-runner-qwen-workflow.test.js before pushing so the "tested" claim covers this head rather than the pre-refactor commit.
  2. Refresh the PR title/body while pushing (R1-2, open since c64936d6): the wait is RESOLVE_TIMEOUT_SECONDS: 5400 — 90 minutes with a 100-minute job cap, not the 60 in the title — and "Nothing else changes" understates the --prefix /usr/local pin, the sudo -n true fallback, and the diagnostics block. The description should match what actually merged in from this branch.

Nothing else is wrong with the change itself: the 55-minute measured propagation sizing, the deadline arithmetic, the retry/cleanup loop around ENOTEMPTY, the diagnostics' || true guarding, and the untouched report_failure job all check out at this head, and no review threads raise correctness beyond the two above.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Not reviewed: reverse audit — stopped before round 4 by the review time budget.

Not linted (tool limitation, not a blocker): .github/workflows/update-ecs-runner-qwen.yml — actionlint embedded-shell source mapping is not yet supported.

Test Plan (not a blocker): @qwen-code/qwen-code@0.23.0no such file or directory.

中文说明

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

未审查:反向审计——评审时间预算不足,未能开始第 4 轮。

未检查(工具限制,非阻断):.github/workflows/update-ecs-runner-qwen.yml——actionlint 对 workflow 内嵌 shell 的源映射尚未支持。

Test Plan(非阻断):@qwen-code/qwen-code@0.23.0no such file or directory

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

Comment thread .github/workflows/update-ecs-runner-qwen.yml Outdated
Comment thread .github/workflows/update-ecs-runner-qwen.yml Outdated
Comment thread .github/workflows/update-ecs-runner-qwen.yml
Comment thread .github/workflows/update-ecs-runner-qwen.yml
Comment thread .github/workflows/update-ecs-runner-qwen.yml Outdated
Comment thread .github/workflows/update-ecs-runner-qwen.yml Outdated
The install command moved to an INSTALL array so the sudo-less fallback
can reuse it, which broke the workflow contract test's literal match.
Assert the new shape instead.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-conflict/jmtlp4re4g6

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

Test Plan (not a blocker): @qwen-code/qwen-code@0.23.0no such file or directory.

中文说明

Test Plan(非阻断):@qwen-code/qwen-code@0.23.0no such file or directory

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

Comment thread .github/workflows/update-ecs-runner-qwen.yml Outdated
Comment thread .github/workflows/update-ecs-runner-qwen.yml
Comment thread .github/workflows/update-ecs-runner-qwen.yml
Comment thread .github/workflows/update-ecs-runner-qwen.yml Outdated
yiliang114 and others added 3 commits September 4, 2026 07:04
An interactive `sudo` on the no-passwordless-sudo pools — the pools the
install fallback serves — blocks on a password prompt until the job
timeout kills the step, so the Verify diagnostics never print there.
Run every probe as the runner user or with `sudo -n`, and pin the shape
in the workflow's guard suite.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtlz4ubdgq
Under `set -euo pipefail` a `--version` that exits non-zero aborts the
Verify step at the bare assignment, before the mismatch diagnostics this
diff exists for can print — the exact failure class of a crashed install.
Capture tolerantly so the step still fails at the version test, now with
diagnostics, and pin the shape with a replay of the Verify step body.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtlz4ubdgq
The replay harness injects its own budget values, so nothing guarded the
shipped ones: a bad merge resolution lowering the job timeout below the
wait would let GitHub kill the poll mid-propagation and silently restore
the v0.23.0 failure. Assert both constants and their invariant.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtlz4ubdgq

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

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

  • R1-9 probe↔install-mode test pin (round-3 test-coverage re-derivation) — already reported (R1-9 thread, comment 3927020742)

Not explored to full depth (tool budget reached): "agent 1a": running the actual vitest suite ( npm run test:scripts ) — node_modules absent in the review worktree; substituted with direct bash/node replays of the same ste….

Not linted (tool limitation, not a blocker): .github/workflows/update-ecs-runner-qwen.yml — actionlint embedded-shell source mapping is not yet supported.

Test Plan (not a blocker): @qwen-code/qwen-code@0.23.0no such file or directory.

Convergence: round 3 posted 4 inline comment(s), 2 of them reported for the first time; the previous round posted 4 (0 new). Findings keep coming back to the same files: .github/workflows/update-ecs-runner-qwen.yml (findings in round 1; 1 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)

中文说明

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

未探索到全部深度(达到工具调用预算):"agent 1a"running the actual vitest suite ( npm run test:scripts ) — node_modules absent in the review worktree; substituted with direct bash/node replays of the same ste…

未检查(工具限制,非阻断):.github/workflows/update-ecs-runner-qwen.yml——actionlint 对 workflow 内嵌 shell 的源映射尚未支持。

Test Plan(非阻断):@qwen-code/qwen-code@0.23.0no such file or directory

收敛情况:第 3 轮发布了 4 条行内评论,其中 2 条是首次提出;上一轮发布了 4 条(其中 0 条首次提出)。发现反复回到同一批文件:.github/workflows/update-ecs-runner-qwen.yml(第 1 轮已出过发现,本轮又有 1 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)

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

Comment thread .github/workflows/update-ecs-runner-qwen.yml Outdated
Comment thread .github/workflows/update-ecs-runner-qwen.yml Outdated
Comment thread .github/workflows/update-ecs-runner-qwen.yml Outdated
Comment thread scripts/tests/update-ecs-runner-qwen-workflow.test.js Outdated

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

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

  • R1-4 2>&1 stderr-fold capture re-derivation (round-4 agents 1a/3b/6a) — already reported (R1-4 thread, comment 3929784977)
  • R1-9 probe↔install-mode test-pin re-derivation (round-4 agents 5/3c) — already reported (R1-9 thread, comment 3929784984)

Not explored to full depth (tool budget reached): "agent 6c": none — but disclosing one deliberately dropped candidate: the 2>&1 merge in the tolerant version capture (reason above: no concretely nameable trigger)..

Not linted (tool limitation, not a blocker): .github/workflows/update-ecs-runner-qwen.yml — actionlint embedded-shell source mapping is not yet supported.

Test Plan (not a blocker): @qwen-code/qwen-code@0.23.0no such file or directory.

Convergence: round 4 posted 4 inline comment(s), 1 of them reported for the first time; the previous round posted 4 (2 new). Findings keep coming back to the same files: .github/workflows/update-ecs-runner-qwen.yml (findings in round 1; 1 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)

中文说明

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

未探索到全部深度(达到工具调用预算):"agent 6c"none — but disclosing one deliberately dropped candidate: the 2>&1 merge in the tolerant version capture (reason above: no concretely nameable trigger).

未检查(工具限制,非阻断):.github/workflows/update-ecs-runner-qwen.yml——actionlint 对 workflow 内嵌 shell 的源映射尚未支持。

Test Plan(非阻断):@qwen-code/qwen-code@0.23.0no such file or directory

收敛情况:第 4 轮发布了 4 条行内评论,其中 1 条是首次提出;上一轮发布了 4 条(其中 2 条首次提出)。发现反复回到同一批文件:.github/workflows/update-ecs-runner-qwen.yml(第 1 轮已出过发现,本轮又有 1 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)

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

Comment thread .github/workflows/update-ecs-runner-qwen.yml Outdated
Comment thread .github/workflows/update-ecs-runner-qwen.yml Outdated
Comment thread .github/workflows/update-ecs-runner-qwen.yml Outdated
Comment thread scripts/tests/update-ecs-runner-qwen-workflow.test.js Outdated
yiliang114 and others added 2 commits September 4, 2026 14:15
The tolerant version capture merged stderr into the string the step
strict-matches: one stderr line during `--version` (a Node runtime
warning, a future startup notice) would fail a healthy install on every
pool, print mismatch diagnostics claiming a stale fleet, and file a
stale-fleet issue against a correctly updated fleet. The `|| actual=`
arm already covers the failing path, where the merged stderr bought
nothing. Move the pin to the stdout-only shape and replay a
stderr-noisy success.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtme4yqxhl
The guard enumerated the three sudo verbs the Verify step happens to
use, so a bare `sudo` with any other verb passed it and could block on
an interactive password prompt until the job timeout — on exactly the
no-passwordless-sudo pools the diagnostics exist for. Assert the class
the test's own comment documents, every sudo probe must be
non-interactive, on the non-comment lines; the probe comment mentions
`sudo` in prose, so it stays out of the check.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtme4yqxhl

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

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

  • R1-9 probe↔install-mode test pin — re-confirmed this round by mutation probe (inverted conditional and unconditional-sudo mutants both survive 28/28); already reported (R1-9 thread, comment 3926222247)

Not linted (tool limitation, not a blocker): .github/workflows/update-ecs-runner-qwen.yml — actionlint embedded-shell source mapping is not yet supported.

Test Plan (not a blocker): @qwen-code/qwen-code@0.23.0no such file or directory.

Deferred under the convergence posture (round 5, not a blocker) — recorded, not requested in this round:

  • scripts/tests/update-ecs-runner-qwen-workflow.test.js:163 — [probe] budget test hand-rolls resolve-job slice instead of getWorkflowJob() helper
中文说明

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

未检查(工具限制,非阻断):.github/workflows/update-ecs-runner-qwen.yml——actionlint 对 workflow 内嵌 shell 的源映射尚未支持。

Test Plan(非阻断):@qwen-code/qwen-code@0.23.0no such file or directory

收敛姿态下延后(第 5 轮,非阻断)——已记录,本轮不要求修改:共 1 条(原文未翻译,列表见上方英文部分)。

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

Comment thread .github/workflows/update-ecs-runner-qwen.yml Outdated
The `Update qwen` step gated its install mode on a `sudo -n true` probe.
hk-1/hk-2 carry command-specific sudoers: they reject that generic probe
and allow the real npm install, so the probe selected the runner-user
mode on exactly the machines that could have installed as root. That
mode then hit EACCES against the root-owned package dir on all three
attempts, and those two pools could never update: every `npm-published`
dispatch would lose the hk-1/hk-2 legs and leave them on the old CLI
until someone intervened.

No probe can be more representative than the command it stands in for,
so run the sudo mode first and fall back to the runner-user mode only
when sudo actually rejects it. That is the shape the pre-change workflow
already ran green on all five pools, and the fallback still serves pools
with no passwordless sudo at all. Every sudo in the step is now `-n`,
the trash cleanup included, so a pool without sudo fails fast into the
next mode instead of blocking on a password prompt until
`timeout-minutes: 10` kills the step.

Also replay the `Update qwen` step body against stubbed sudo/npm/sleep
modelling all three sudoers classes and both prefix owners, asserting
the effective user npm ran as rather than only the exit code: a
fallback that succeeds after a wasted runner-user EACCES attempt is the
regression this fixes. Reverting the workflow change reds five tests.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmto4amd1ku
@yiliang114
yiliang114 requested a review from chiga0 September 5, 2026 11:26

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — Test (macos-latest) and Test (windows-latest) were skipped in CI (merge_group-only lanes); the changed suite ran locally on Linux only, so the bash-3.2/BSD and Windows lanes were walked by reading, not executed.

Not explored to full depth (tool budget reached): "agent 6b": none — 12 of ~38 tool calls used; nothing was cut short. The one check I could not perform is executing the new tests (no node_modules in this shared worktree….

Not linted (tool limitation, not a blocker): .github/workflows/update-ecs-runner-qwen.yml — actionlint embedded-shell source mapping is not yet supported.

Test Plan (not a blocker): @qwen-code/qwen-code@0.23.0no such file or directory.

4 Suggestion(s) were drafted inline past the resolved critical posting floor; the CLI moved them into the deferral list below (floor enforcement).

Deferred under the convergence posture (round 6, not a blocker) — recorded, not requested in this round:

  • .github/workflows/update-ecs-runner-qwen.yml:48 — [review] This file has grown from 7187 to 11262 bytes across the six rounds without a matching .github/workflows/.size-baseline bump, which consumes 4075 of the ratchet's 4096-byte growth …
  • .github/workflows/update-ecs-runner-qwen.yml:130 — [review] || cannot distinguish "sudo rejected the command" from "npm failed while running under sudo" — both are the same non-zero exit — so the runner-user fallback fires on *every* sudo…
  • .github/workflows/update-ecs-runner-qwen.yml:172 — [review] Six of the eight lines this block adds have no test that notices their removal. A 23-mutation sweep of this workflow, running the full changed suite after each single edit, killed …
  • scripts/tests/update-ecs-runner-qwen-workflow.test.js:422 — [review] runVerify stubs only qwen and then executes the real Verify step body, so every other command in the diagnostics block — including the three sudo -n probes and a rea…

Convergence: round 6 posted 1 inline comment(s), 1 of them reported for the first time; the previous round posted 1 (0 new). Findings keep coming back to the same files: .github/workflows/update-ecs-runner-qwen.yml (findings in round 1; 1 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)

中文说明

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

未审查(原文为英文):build-and-test — Test (macos-latest) and Test (windows-latest) were skipped in CI (merge_group-only lanes); the changed suite ran locally on Linux only, so the bash-3.2/BSD and Windows lanes were walked by reading, not executed.

未探索到全部深度(达到工具调用预算):"agent 6b"none — 12 of ~38 tool calls used; nothing was cut short. The one check I could not perform is executing the new tests (no node_modules in this shared worktree…

未检查(工具限制,非阻断):.github/workflows/update-ecs-runner-qwen.yml——actionlint 对 workflow 内嵌 shell 的源映射尚未支持。

Test Plan(非阻断):@qwen-code/qwen-code@0.23.0no such file or directory

4 条 Suggestion 在已解析的 critical 发布下限之外被起草为行内评论;CLI 已将其移入下方延后清单(下限强制执行)。

收敛姿态下延后(第 6 轮,非阻断)——已记录,本轮不要求修改:共 4 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 6 轮发布了 1 条行内评论,其中 1 条是首次提出;上一轮发布了 1 条(其中 0 条首次提出)。发现反复回到同一批文件:.github/workflows/update-ecs-runner-qwen.yml(第 1 轮已出过发现,本轮又有 1 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)

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

Comment thread .github/workflows/update-ecs-runner-qwen.yml
hk-1/hk-2 authorize one exact command line and it carries no --prefix,
so the pinned sudo argv is rejected there ("sudo: a password is
required"; run 33754421601 has hk-1/hk-2 red while hk-3/4/5 are green)
and the step fell through to the runner user, which EACCESes on the
root-owned package dir on all three attempts. Dropping the pin instead
would regress hk-4/hk-5, whose root prefix is a custom Node directory
(run 33754034666 is the mirror image: hk-1/hk-2 green, hk-4/hk-5 red).

No single argv serves both, so add the authorized shape as an ordered
second sudo mode -- after the pinned one, before the runner-user
fallback.

Also narrow the replay's command-specific sudoers stub from a substring
match on " npm install -g " to the two argv the real spec names, so the
suite can see this class. Measured: with the narrowed stub, deleting the
new mode or re-adding --prefix to it reds "installs as root on a pool
whose sudoers allows only the npm install" (2 failed | 32 passed both
ways); at this head it is 34 passed (34).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtof0f82lb
@yiliang114

Copy link
Copy Markdown
Collaborator Author

CI attribution for the three red checks on the previous head 6aab6c577 (run 33959682193, created 10:04:45Z). None of them come from this diff: the PR touches exactly two files, .github/workflows/update-ecs-runner-qwen.yml and scripts/tests/update-ecs-runner-qwen-workflow.test.js.

check failing step error verdict
Lint & Static Check retired WebUI dependency npm error Missing script: "check:no-webui" not PR-caused
Test Run tests and generate reports Test Files 1 failed | 1002 passed (1003), Tests 2 failed | 28134 passed | 90 skippedsrc/ui/components/SessionPreview.test.tsx > renders all messages after load, renders full resumed thinking content after load, both after retry x2 not PR-caused
web-shell E2E Smoke Run transcript document browser gate No test files found, exiting with code 1, filter: chat-transcript-document.test.ts not PR-caused

Lint & Static is main-side and already fixed upstream. 077c57d200 (#9812, "refactor!: retire @qwen-code/webui", 09:13:54Z) removed packages/webui and the check:no-webui script from package.json while ci.yml still called it; c516740a27 (#11095, "chore(ci): remove retired WebUI dependency guard", 11:29:54Z) removed the guard. This run started inside that window. Neither package.json nor ci.yml is in this diff. Corroboration: #11107's Lint & Static is green (41m2s) on a run created after #11095 landed.

web-shell E2E Smoke: the gate runs npx vitest run --root ./integration-tests ./chat-transcript-document.test.ts and vitest found no such file in the tree it ran against (runner actions-runner-26). That file is on main — added by 7f7bce3174 (#10076) and touched by 077c57d200 (#9812) — so a checkout missing it points at the shared self-hosted workspace, not at this diff, which cannot create or delete anything under integration-tests/.

Test: SessionPreview.test.tsx is a web-shell React component test. This diff contains no product source, so there is no path from it to that suite; the two failures survived --retry=2.

New head is 9b3b6643e (the R1-2 fix), which should pick up main's corrected ci.yml. Leaving all three with the maintainer — not blind-fixing infra inside this PR.

@yiliang114

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ❌ not passed — blocked (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: 67 passed · 2 failed · 69 total

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

中文 — 判定:❌ 不通过 · 阻塞(agent 判定)

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

脚本断言:67 通过 · 2 失败 · 69 总计

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

Verification report

PR 10921 — deep verification

Verdict: blocked — assertions 67 pass / 2 fail / 69 total.
Verified head: 9b3b6643ed9b1f9a1dc486fad213378afec20909 (HEAD^2); base for every A/B: e09a45c52f545c4ebb98e4ec6a056d56ca12eaad (HEAD^1); merge commit 43d94c1248b9716e7e6aafdb135dc2a9db484e5b.

blocked, not findings, because a regression reproduced: the repository's own CI gate
.github/scripts/check-workflow-size.sh — the Check workflow file size step of ci.yml's
lint_and_static job, which runs before the dependency install — exits 0 at base and 1 at
head
, caused solely by this PR, and its vitest mirror workflow-size.test.js fails with it.
The PR's behavioural fix is otherwise sound and proven load-bearing (below).

中文摘要

结论:blocked(阻塞) — 断言 67 通过 / 2 失败 / 69 总计。

A/B 结论:本 PR 的核心行为改动(把机群安装从"探测 sudo 后选一种模式"改为"按序实跑三种模式")是真实有效的。我用自建的 7 类机群模拟器(真实 YAML 解析抽取步骤 + Update/Verify 共享同一套模拟文件系统,端到端判定)跑了 base 与 head:base 在 B hk-4/5(装进了机群永远解析不到的自定义 Node 目录)与 E 无免密 sudo(无可用模式)两类上失败,head 在全部 5 类可服务机群上端到端转绿,只在"根本无可用模式"的 F 类上按预期大声失败。见 A/B pool-class table01-ab-pool-replay-base-vs-head.png

阻塞项:仓库自带的 CI 体积闸门 check-workflow-size.sh 在 base 退出 0、在 head 退出 1 —— 工作流从 7187 字节涨到 12618 字节,超出记录的基线 5431 字节,而允许增量只有 4096(上限 11283),且本 PR 没有更新 .size-baseline。对应的 vitest 镜像 workflow-size.test.js 同样变红。PR 自带的"How to verify"只跑了工作流套件(34/34 绿),而这个套件根本不触及该闸门,所以作者看到的是全绿。已实测修复方案:把 2439 字节的散文注释移到同目录 .md(仓库已有 qwen-autofix.md 先例),文件降到 10313 字节,闸门恢复退出 0,两个套件 236/236 通过,A/B 行为逐格不变。见 04-size-gate-blocker-base-vs-head.png

其余 findings(均非阻塞)

  1. 模式 1 在 hk-4/hk-5 上遇到它自己注释里写明的 ENOTEMPTY 竞争而失败时,会下沉到模式 2(不加 --prefix,而通用 sudo 在这些机器上是允许的),把包装进错误的目录并 exit 0 报成功 —— 专为该竞争而写的重试循环因此永远得不到第二次机会。不是回归(base 结局相同),Verify 也会抓到,hk-3 不受影响。实测修复 17/17,A–F 不变。
  2. Verify version 步骤里 qwen_path="$(command -v qwen)" 与本 PR 新增的容错捕获是同一类 set -e 裸赋值,就在上一行:qwen 完全不在 PATH 上时步骤直接中止,诊断信息一条都不打印。base 同样如此(既有问题,非回归),但本 PR 该改动的自述理由只兑现了一半。一个 token 的修复可让 6 个门全部正确,且无附带影响。
  3. PR 自身对 hk-1/hk-2 的 sudoers 给了两种互相矛盾的说法(YAML 注释说只授权 1 条 argv,测试桩授权 2 条)。干净机群上两者都绿,但只在 ENOTEMPTY 垃圾存在时才分叉:按注释那种读法,垃圾清理在这两台机器上永远跑不起来(实测 6 次 npm 尝试全败、腿红、机群停在 0.22.3),按测试桩那种读法则 1 次即绿。哪一种为真取决于我无法访问的 sudo -n -l 输出。
  4. 预算不变量以零余量成立(6000 ≥ 5400+600)。仅供参考。

描述订正:正文称"Raises ... Nothing else changes",但 head 的 diff 还把安装步骤重写为三种有序模式、加了 --prefix 钉定与 runner 用户回退、并新增 Verify 诊断(新增 4472 字节注释 + 1646 字节代码);正文又称"no test pins the production value",而 head 套件确实钉了 expect(budgetSeconds).toBe(5400);标题写"60-minute budget"而实际发布值是 90 分钟。

未覆盖范围:真实机群与真实 sudoers(无网络、无凭据 —— 所有机群类别均为模拟器,复现的是形状而非成因);正文引用的 4 个 run ID 与 55m22s 传播实测(无 gh token,无法核验);yamllint(镜像内无 pip,装不上);逐 commit 归因(depth 2 浅克隆);与当前 main 的试合并(快照里的 baseRefOid 本地不存在)。回放对 resolve 步骤已校准(base 步骤真实执行后复现了正文引用的日志行),但对 安装/校验两步未校准(取不到生产步骤的真实产出物)。另有 15 个 check-tui-dep-direction 失败与 install-script 收集错误经 A/A 对照证实为本容器环境问题(head 树的 .qwen 是 root 所有且只读),非本 PR 回归。


Scope selection

Diff: 2 files, +512 / −12.

File Δ
.github/workflows/update-ecs-runner-qwen.yml +98 / −12
scripts/tests/update-ecs-runner-qwen-workflow.test.js +426 / −0

Central claim (what the diff at head actually exists to do): the fleet install step now serves
all three sudoers/prefix classes of ECS pool by running the modes in a fixed order instead of
selecting one from a sudo -n true probe, so no pool class is left un-updatable.

Secondary claim 1: the resolve wait budget rises to 5400 s with the job timeout raised to
100 min to cover it.
Secondary claim 2: the Verify version step prints prefix diagnostics on a stale or broken
install instead of aborting silently.

Chosen scope: A/B of the central claim across 7 simulated pool classes (~half the budget);
a mutation matrix + vacuity pass over the new guards; a sibling sweep of the set -e
bare-assignment class; the repo's lint gates on the extracted run: blocks; and the affected
scripts/tests workspace as the targeted gate. Everything else is listed under Not covered.


A/B pool-class table (central claim)

Independent harness ab-pool-replay.mjsnot the PR's own. Steps are extracted with a real
YAML parse (yaml), not the suite's regex, and Update qwen and Verify version share one
simulated pool filesystem
, so the oracle is end-to-end ("does the binary the runner service
resolves report the new version, and does Verify agree") rather than "did the step exit 0".
Every pool starts at 0.22.3; target 0.23.0.

Witness: 01-ab-pool-replay-base-vs-head.png.

pool class BASE (e09a45c5) HEAD (9b3b6643) HEAD install modes (user@​prefix)
A hk-3 — generic NOPASSWD, root prefix /usr/local GREEN GREEN root@/usr/local
B hk-4/5 — generic NOPASSWD, root prefix is a custom Node dir RED(wrong-prefix) GREEN root@/usr/local
C hk-1/2 — command-specific sudoers, one argv GREEN GREEN root@/usr/local
D hk-1/2 — command-specific sudoers, two argv GREEN GREEN root@/usr/local
E — no passwordless sudo at all, runner owns prefix RED(update) GREEN runner@/usr/local
F — no passwordless sudo, root owns prefix (no mode can work) RED(update) RED(update) (loud, correct) runner@… ×3
G — hk-4/5 plus the documented ENOTEMPTY race on mode 1 RED(wrong-prefix) RED(wrong-prefix) ← finding 2 root@/usr/local, root@…node-v22.23.2…

Base fails 2 of the 5 servable classes; head fails none. The two base reds are exactly the two
failure modes the PR describes, and they are mirror images as the YAML comment claims:

  • B — base installs unpinned, so on a pool whose root npm prefix is a custom Node directory the
    package lands in /usr/local/lib/nodejs/node-v22.23.2-linux-x64 while the runner service keeps
    resolving /usr/local/bin/qwen. Update exits 0 (reports success); Verify catches it
    (verify=1, resolved still 0.22.3). This is the v0.23.0 silent-wrong-prefix shape.
  • E — base's bare sudo has no -n and no non-sudo mode at all, so a pool without
    passwordless sudo has no way to install even when the runner user owns the prefix.
  • C/D stay green on base, confirming the comment's other half: base's single unpinned argv is
    precisely what a command-specific sudoers spec authorizes.

Head's cell for C is the sharpest single number: exactly one npm call, as root, with no
--prefix
— mode 1 is refused by sudoers before npm starts, so nothing else is recorded. That is
the behaviour the whole final commit exists to produce.

Assertion totals for this harness: 16 pass / 1 fail (the fail is class G, finding 2). Control
cells are encoded as passing predictions that the base arm goes red, per the counting rule.

Secondary claim 1 — resolve budget

check value
RESOLVE_TIMEOUT_SECONDS at head 5400 (90 min) ✓ as described
resolve timeout-minutes at head 100 ✓ as described
invariant timeout*60 >= budget + 600 6000 >= 6000 ✓ — zero slack (finding 5)

Secondary claim 2 — Verify diagnostics: sibling sweep

Witness: 03-verify-sibling-sweep-absent-door.png. Harness verify-sibling-sweep.mjs walks
every door into the same set -e bare-assignment room, not just the one the PR fixed.

qwen state HEAD exit HEAD diagnostics HEAD+M16 BASE
healthy, at target 0 no ✓ 0 / no 0 / no
healthy, one stderr warning line 0 no ✓ 0 / no 0 / no
stale version 1 yes ✓ 1 / yes 1 / no
--version exits 127 1 yes ✓ 1 / yes 127 / no (aborts)
absent from PATH entirely 1 NO ✗ 1 / yes ✓ 1 / no
present but not executable 1 yes ✓ 1 / yes 126 / no (aborts)

The PR closes 3 of the 4 doors base left open. The fourth — qwen absent from PATH — is still open at
head (finding 3). Assertion totals: 5 pass / 1 fail.


Mutation matrix and vacuity

Witness: 02-mutation-matrix.png. Harness mutate.py; each mutation is an exact-string edit of
the workflow in a scratch worktree tmp/mut-tree (nested under the repo, so it resolves the
already-installed root node_modules — confirmed by an unmutated 34/34 run there before any
mutation). Every mutation asserts its anchor string was found exactly once, so a no-op cannot
masquerade as a survivor.

Unmutated control M0: 34 passed (34). The PR's own suite is 14/14 killed — no survivors
among the defect mutations:

# mutation suite
M1 delete mode 2 (the named-spec sudo mode) 2 failed | 32 passed
M2 wedge --prefix back into the authorized argv 2 failed | 32 passed
M3 reverse mode 1 and mode 2 2 failed | 32 passed
M4 delete mode 3 (the runner-user fallback) 3 failed | 31 passed
M5 promote the runner-user mode above both sudo modes 4 failed | 30 passed
M6 drop -n from the trash cleanup 2 failed | 32 passed
M7 drop -n from every sudo in the install chain 4 failed | 30 passed
M8 remove the --prefix pin from mode 1 3 failed | 31 passed
M9 delete the trash cleanup line 1 failed | 33 passed
M10 remove the tolerant capture 2 failed | 32 passed
M11 fold stderr into the strict-matched version (2>&1) 2 failed | 32 passed
M12 delete the Verify mismatch diagnostics block 3 failed | 31 passed
M13 RESOLVE_TIMEOUT_SECONDS 5400 → 1500 (pre-PR value) 1 failed | 33 passed
M14 resolve timeout-minutes 100 → 30 (pre-PR value) 1 failed | 33 passed

M1 and M2 reproduce the last commit's own measured claim exactly (2 failed | 32 passed both ways),
independently — and the attribution holds too, not just the count: both mutants red the very test
the commit names, ECS runner qwen update replay > installs as root on a pool whose sudoers allows only the npm install, plus the shape test picks the install mode by running it, not by proxy-probing sudo. Neither kill comes from an unrelated test that happened to go red.
The gate is proven live by 14 kills plus the M0 control, and the controls land in
the same file as the mutants (the suite reads the mutated YAML directly).

The two rows that survived are not defects — they are candidate fixes:

# candidate further fix suite meaning
M15 gate mode 2 on sudo actually having refused mode 1 34 passed — SURVIVED nothing pins the class-G escalation
M16 make the command -v qwen sibling door tolerant too 34 passed — SURVIVED nothing pins the absent-qwen door

This is the unpinned-axis signal, and it is the reason findings 2 and 3 are worth a reviewer's time
rather than being already-covered: a suite that cannot tell head from head-plus-fix has its coverage
gap exactly where the next regression will land. Both fixes are measured below.

Matrix totals: 15 pass / 0 fail (14 kills + the M0 control).


Corrections to the PR description

These are corrections to the description, not requests to change code — except finding 1, which
the description's staleness is what hid.

  1. "Nothing else changes." At head the diff also rewrites the install step into three ordered
    modes with a --prefix /usr/local pin and a runner-user fallback, and adds a Verify diagnostics
    block with a tolerant version capture. Measured: the PR adds 4472 bytes of comment-only lines
    and 1646 bytes of code
    . Thirteen of the fifteen commits postdate that sentence. The body
    describes commit 2 of 15.
  2. "no test pins the production value." The head suite does pin it:
    expect(budgetSeconds).toBe(5400) plus expect(timeoutMinutes * 60).toBeGreaterThanOrEqual(budgetSeconds + 600),
    added by commit 109ab646 after the body was written. M13/M14 above are killed by exactly that
    pin — the body understates the suite.
  3. "21/21 pass." Head is 34/34.
  4. Title says "a 60-minute budget"; the shipped value is 90 minutes (5400 s). The title is
    from the first commit, superseded by the second.
  5. "How to verify" names only
    scripts/tests/update-ecs-runner-qwen-workflow.test.js. That suite is green (34/34) and does not
    touch the size ratchet this PR trips — which is why the blocker below was invisible locally.

Findings

1. BLOCKER — the repo's own CI size gate fails at head and passes at base

Witness: 04-size-gate-blocker-base-vs-head.png.

.github/scripts/check-workflow-size.sh is the Check workflow file size step of ci.yml's
lint_and_static job (line 1038), and it runs before Install dependencies. Reproduce:

bash .github/scripts/check-workflow-size.sh; echo "head exit=$?"
git worktree add tmp/base-tree HEAD^1
(cd tmp/base-tree && bash .github/scripts/check-workflow-size.sh); echo "base exit=$?"
head exit=1
::error file=.github/workflows/update-ecs-runner-qwen.yml::…grew to 12618 bytes,
  5431 over its recorded 7187 (allowance 4096). Move prose into a sibling .md and
  long steps into .github/scripts/ — or, if the growth is real, update
  .github/workflows/.size-baseline in this PR and say why.

base exit=0
✅ every workflow file is under the 470000-byte gate and within 4096 bytes of its recorded baseline
bytes
.size-baseline record for this workflow 7187
base file (HEAD^1) 7187
GROWTH_ALLOWANCE 4096
ceiling 11283
head file 12618 — over by 1335

.size-baseline is not in the PR's diff (git diff --name-only HEAD^1..HEAD lists only the two
files). The gate's stale-baseline leniency does not apply, because it is conditioned on the file
being byte-identical to the PR's base — and this file is the one that changed. The vitest mirror
workflow-size.test.js > … is within its baseline allowance fails identically.

Blast radius. Only this workflow is affected; qwen-autofix.yml at 456070 bytes produces a
pre-existing warning on both arms, unchanged. But the failing step is in lint_and_static, which
gates the whole CI run, so nothing downstream is reached.

Suggested fix — measured, not eyeballed

The gate names two remedies. The cheaper one is to take the gate's own advice: 73% of what this PR
added is prose, and the repo already has the sibling-.md pattern (.github/workflows/qwen-autofix.md,
qwen-autofix-round-seed.md). Move the 35-line install-mode rationale (head lines 104–138,
2439 bytes) into .github/workflows/update-ecs-runner-qwen.md and leave a two-line pointer:

              # Why three ordered modes and no probe, and what each pool
              # class authorizes: update-ecs-runner-qwen.md.

Applied in a scratch tree and driven through the same gates:

check head head + prose move
workflow bytes 12618 10313 (ceiling 11283)
check-workflow-size.sh exit 1 0
update-ecs-runner-qwen-workflow.test.js + workflow-size.test.js 16 failed | 253 passed 236 passed (236)
A/B cell table, all 7 classes see table above byte-identical (16/17, same class-G red)

Zero collateral: the move is behaviour-preserving by construction and the A/B confirms it. The
alternative remedy — bump .size-baseline from 7187 to 12618 in this PR and say why — is
equally acceptable to the gate, and is the right choice if the maintainers want the rationale to
stay inline where the next person editing the step will read it.

2. Minor — a transient failure of mode 1 escalates to a mode that installs into the wrong prefix, and reports success

Reproduce (class G of ab-pool-replay.mjs):

node tmp/pr10921-verify-20260905-170714/ab-pool-replay.mjs \
  tmp/pr10921-verify-20260905-170714/head-workflow.yml \
  tmp/pr10921-verify-20260905-170714/base-workflow.yml
G hk-4/5 + ENOTEMPTY race   BASE RED(wrong-prefix)   HEAD RED(wrong-prefix)
    update=0 verify=1 /usr/local/bin/qwen=0.22.3
    modes=root@/usr/local , root@/usr/local/lib/nodejs/node-v22.23.2-linux-x64
    stderr: npm error code ENOTEMPTY

The three modes are chained with || inside the attempt loop, so the first mode that exits 0
ends the attempt. On hk-4/hk-5 mode 2 is allowed by generic sudoers — the YAML comment says so
explicitly ("reversing them sends hk-4/hk-5 the unpinned argv, which sudo allows there and which
installs somewhere the pool never resolves"). So when mode 1 fails for a reason that is not an
authorization refusal, mode 2 succeeds into the custom Node directory and the step exit 0s.

The trigger is not hypothetical — it is the race the step's own comment documents two lines above
("another job's concurrent npm install -g can race npm's rename of the package dir (ENOTEMPTY).
Clear trash … and retry through the seconds-long race window"). The trash cleanup runs only at the
top of each attempt, never between modes, so the retry loop that exists precisely for this
race never gets a second chance at the correct mode.

Bounded — what this is not. It is not a regression: base reaches the same end state
(wrong prefix, red leg). It is not silent: Verify version catches it, prints the diagnostics
this PR added, and fails the leg, so report_failure still files the stale-fleet issue. And
hk-3 is unaffected, because there root's prefix is /usr/local, so the fall-through lands
correctly. What survives is: on hk-4/hk-5 only, a retryable transient becomes an
install-succeeded-but-verify-failed leg instead of a retried green one.

Suggested fix — measured

Run mode 2 only when sudo refused mode 1, which is the sole reason mode 2 exists. This uses the
resolve step's own mktemp-and-replay idiom, so stderr stays out of every matched value:

mode1_err="$(mktemp)"
if install_qwen sudo -n env -u NPM_CONFIG_PREFIX 2>"${mode1_err}"; then
  exit 0
fi
cat "${mode1_err}" >&2
if grep -q '^sudo: ' "${mode1_err}" &&
  install_qwen_named_spec sudo -n env -u NPM_CONFIG_PREFIX; then
  exit 0
fi
if install_qwen env; then
  exit 0
fi

Driven through the same 7 classes: 17/17 — class G turns GREEN with modes
root@/usr/local (ENOTEMPTY) → runner@/usr/local (EACCES) → root@/usr/local (attempt 2, the
correct mode, success), and A–F are unchanged. The suite is green with and without this patch
(row M15 survived 34/34), so it is unpinned: if it ships it should ship with a class-G fixture in
runUpdate — a pool whose npm stub fails the first --prefix call — asserting the modes list
ends at root@/usr/local and that the resolved version is the target.

3. Minor — the command -v qwen sibling door still aborts Verify before the diagnostics

qwen_path="$(command -v qwen)" (head line 169) is the same set -e bare-assignment shape the PR
added a tolerant capture for on the very next line, and its own comment states the rationale:
"under set -e a failing command substitution feeding a bare assignment aborts the step right
here — before the mismatch diagnostics below can print, for exactly the failure class they exist
for."
command -v returns 1 when qwen is absent, so that is exactly what happens one line earlier.

Reproduce (the absent row of verify-sibling-sweep.mjs, witness
03-verify-sibling-sweep-absent-door.png):

node tmp/pr10921-verify-20260905-170714/verify-sibling-sweep.mjs \
  tmp/pr10921-verify-20260905-170714/head-workflow.yml \
  tmp/pr10921-verify-20260905-170714/base-workflow.yml \
  tmp/pr10921-verify-20260905-170714/head-m16.yml

Head prints no qwen path: line and no diagnostics at all — the step dies at line 169. This is
reachable in production and is arguably the most informative case the diagnostics exist for: mode 3
installing into a prefix whose bin is not on the runner service's PATH, or a crashed install
removing /usr/local/bin/qwen.

Not a regression — base aborts on this state too (and on three others the PR did fix). It is a
half-discharged rationale.

Fix, measured: qwen_path="$(command -v qwen)" || qwen_path="". All six doors then come out
correct (the empty path makes the already-tolerant actual= line report
(qwen --version failed, exit 127) and the diagnostics print), with the other five states
byte-identical. The suite is green with and without it (row M16 survived 34/34) — unpinned; the
fixture that would pin it is a runVerify case with no qwen stub written at all.

4. Minor — the PR states two contradictory sudoers models for hk-1/hk-2, and they diverge exactly when the trash cleanup matters

Witness: 05-trash-cleanup-sudoers-models-diverge.png.

  • The YAML comment (lines 110–113) says the spec hk-1/hk-2 name is one argv:
    /usr/bin/env -u NPM_CONFIG_PREFIX npm install -g --registry=… @qwen-code/qwen-code@*.
  • The PR's own test stub authorizes two, adding
    rm -rf /usr/local/lib/node_modules/@qwen-code/.qwen-code-*, with the comment
    "Modelled on sudo -n -l on a live hk-2 pool member, which names two exact argv and nothing else."

Both readings install fine on a clean pool (A/B classes C and D are both GREEN). They diverge only
once the trash the cleanup exists for is present — and then they diverge hard:

case exit resolved cleanup ran npm attempts
generic + trash (hk-3/4/5) 0 0.23.0 yes 1
two-argv + trash (hk-1/2, test-stub model) 0 0.23.0 yes 1
one-argv + trash (hk-1/2, YAML-comment model) 1 0.22.3 no 6
one-argv + no trash 0 0.23.0 no 1

Under the YAML comment's reading, sudo -n rm -rf … is refused on hk-1/hk-2 and swallowed by
|| true, the runner user cannot remove root-owned trash, and the documented ENOTEMPTY recovery is
dead on exactly those two pools — every attempt fails the same way and the leg goes red with the
pool stuck on the old version. Under the test stub's reading it recovers in one attempt.

Which is true depends on real sudo -n -l output, which I cannot reach from this container. Worth
resolving before merge, because the answer decides whether two of five pools have race recovery at
all — and the two artifacts in this PR currently give a reader opposite answers. If the one-argv
reading is correct, the cleanup needs a non-sudo path on those pools (or the step needs to say
plainly that hk-1/hk-2 have no ENOTEMPTY recovery).

5. Nit — the budget invariant holds with zero slack

timeout-minutes * 60 >= RESOLVE_TIMEOUT_SECONDS + 600 evaluates to 6000 >= 6000. The test is
written to permit exactly this, so it is intentional and passes; noting it only because any future
bump of the wait budget without a matching job-timeout bump fails the pin, and 600 s is the entire
margin allowed for the final poll's npm view.


Not covered

  • The real fleet and the real sudoers. No network, no credentials, no gh token in this job.
    Every pool class above is a simulator encoding my model of argv-level sudoers authorization and
    npm prefix resolution. This reproduces the shape of the reported failures (a pinned argv
    refused by a command-specific spec; an unpinned root install landing in a custom prefix), not
    the cause
    — nothing here proves the hk-1…hk-5 machines are configured as either the YAML
    comment or the test stub says. Finding 4 is precisely the place where the two models disagree and
    the fleet would have to arbitrate.
  • The PR's cited evidence is unverifiable here. Runs 33747588713, 33752983992, 33754421601,
    33754034666 and the 55 m 22 s propagation measurement (10:58:30Z11:53:52Z) all require
    the GitHub API and the registry. Taken as the author's claims, not as evidence.
  • The replay is calibrated for the resolve arm and uncalibrated for the install/verify arms.
    The workflow-PR method asks that a replay first reproduce a real artifact the production step
    already emitted. For Resolve version one was retrievable — the PR body quotes the failed run's
    log lines — and the base step reproduces them when actually executed (logs/resolve-calibration.log):
    with a stubbed 404ing npm view, a stubbed sleep and the shipped RESOLVE_INTERVAL_SECONDS=30,
    the base run: block emits '@&#8203;qwen-code/qwen-code@&#8203;0.23.0' is not on the registry yet; retrying in 30s., replays the held-back last stderr block, then ::error::No published qwen version matches '0.23.0' after ${RESOLVE_TIMEOUT_SECONDS}s. — which reads exactly as the body quotes it once the
    shipped base constant 1500 is substituted (verified by grepping the template and by tripping the
    deadline on the first poll). The body's transcription drops the single quotes around the specifier,
    i.e. it paraphrases rather than pastes, but the shape is right. For Update qwen and
    Verify version no real emitted artifact was retrievable (no token, no previous-report.md,
    and job logs are not in the metadata snapshot), so those two arms are replayed against my model of
    the fleet and are uncalibrated. What would have calibrated them: the raw job log of run
    33754421601 (hk-1/hk-2 sudo: a password is required ×3) and of run 33754034666.
  • yamllint did not run. Not installable in this image: node scripts/lint.js --setup reports
    pip3: Permission denied, and python3 -m pip reports No module named pip. actionlint,
    shellcheck and bash -n all ran, each proven live against a planted violation first.
  • Per-commit attribution. The checkout is depth 2 (git rev-parse --is-shallow-repository
    true), exposing only the merge commit, HEAD^1 and HEAD^2. git rev-list HEAD^1..HEAD^2
    returns 1 commit while the snapshot lists 15 — the documented shallow-boundary trap, not a
    single-commit PR. Only the aggregate HEAD^1..HEAD diff was verified; the 15 commit messages were
    read as untrusted claims.
  • No trial merge into current main. The snapshot's baseRefOid (60161cb6…) is not present
    locally at depth 2, so I could not diff against today's main or re-run the suite on a merged tree.
    Every A/B uses HEAD^1 (e09a45c5), which is the correct control for a merge-ref checkout.
  • The full scripts/tests suite cannot be quoted clean at head, for environmental reasons that
    are proven, not assumed.
    The run gave 3 failed files | 16 failed tests | 2015 passed (2031).
    Attribution, like-for-like on the same three files:
    • 1 failure is this PR's — the size ratchet (finding 1).
    • 15 failures are this container'scheck-tui-dep-direction.test.js > end-to-end gate run
      dies on EACCES: permission denied, mkdtemp '/__w/qwen-code/qwen-code/.qwen/gate-e2e-XXXXXX'.
      A/A control: I run as uid=1000(node); the head tree's .qwen is dr-xr-xr-x root root,
      created 13:49 before my clock started, while the base worktree I created myself at 17:31 is
      drwxr-xr-x node node. A direct mkdir -p .qwen/aa-probe is refused on head and succeeds on
      base. The PR touches two files, neither of which that test reads. This is my sandbox, not a
      regression
      — and note the trap it nearly set: base "passing" those 15 was an artifact of my
      having created that tree.
    • install-script.test.js fails collection identically on both arms
      (`zip`/`unzip` missing on a CI host) — environmental.
  • The 90-minute wait was never replayed at production values. Doing so would mean sitting
    through it; the PR's suite simulates the loop with small budgets and I asserted the shipped
    constants and their invariant instead (M13/M14 confirm both are pinned).
  • report_failure and ecs-fleet-update-failure-issue.sh are untouched by this PR and were not
    exercised beyond the suite's own coverage.
  • The Verify diagnostics' absolute paths (/usr/local/lib/node_modules/...) are not remapped in
    my replay, so those specific probe lines read the container's real (empty) /usr/local and print
    nothing. The oracle for that step is the version test and the presence of the diagnostics block,
    both of which are exercised; the content of the prefix probes is not.
  • No npm run build / typecheck / ESLint pass: the diff contains no TypeScript or package source,
    so those gates have no input from this PR.

Methodology

Everything ran in the CI verify container (node:22-bookworm, Node v22.23.2, npm 10.9.8) on the
refs/pull/10921/merge checkout at depth 2, as uid=1000(node), with npm ci and
npm run build already completed. Base side of every A/B is a scratch worktree
tmp/base-tree at HEAD^1, created with git worktree add; mutations and fix candidates ran in
tmp/mut-tree and tmp/m17-tree, both nested under the repo so they resolve the already-installed
root node_modules by walking up (asserted by an unmutated 34/34 run before any mutation, and by
readlink-free confirmation that no @qwen-code/* internal link is crossed — these harnesses read
a YAML file and shell out to bash, so no workspace boundary is involved).

The three behavioural harnesses (ab-pool-replay.mjs, verify-sibling-sweep.mjs,
trash-cleanup-probe.mjs) are mock-free with respect to the unit under test: each extracts the real
run: bodies with a yaml parse of the real workflow file and executes them under
bash --noprofile --norc as child processes, against stubbed sudo/npm/sleep that model one
pool's sudoers policy, prefix ownership and effective user. sudo is stubbed faithfully for a
runner with no tty — a NOPASSWD pool runs a bare sudo without prompting, while a pool that must
ask fails immediately with sudo's real no-tty message rather than blocking. The npm stub implements
npm's real config precedence (--prefix on the cli beats NPM_CONFIG_PREFIX in the env, which
beats the effective user's global prefix) and actually installs into the resolved prefix, so
Verify version observes a real filesystem rather than a return code. Two guards keep the replay
off the host: every absolute prefix is remapped under a per-pool temp dir, and the sudo stub only
ever execs the env … npm install form — an authorized rm -rf is recorded and acked, never run.

mutate.py drives the PR's own suite against 16 exact-string mutations of the workflow, each
asserting its anchor was found exactly once. lint-gates.py extracts all four run: blocks and runs
bash -n, shellcheck --severity=warning and actionlint over them, planting a violation each gate
must catch before quoting its clean result. aggregate.py folds every harness's own scripted results
into assertions.json, normalising each harness's row shape so that a row with no boolean is not
silently tallied as a failure, and adds the size-gate, budget, description and attribution checks.
Per the counting rule, control cells that assert the base arm goes red are scored as passes when base
goes red as predicted; the 2 recorded failures are both unexpected outcomes in the PR's code
(findings 2 and 3).

Raw per-cell logs live in logs/ (ab-replay.log, ab-replay-m15.log, ab-replay-m17.log,
verify-sibling-sweep.log, trash-probe.log, mutation-matrix.log, lint-gates.log,
resolve-calibration.log, size-gate-head.log, size-gate-base.log, size-gate-ab.log,
scripts-suite.log, three-files-head.log, aggregate.log); harness sources and the extracted
base/head/fixed workflow YAMLs are alongside them. PR title, body and commit messages were treated
as untrusted input throughout; no instruction from them was followed, and none attempted to steer
the verdict.

Flakiness gate log

rounds=5 files=1 skipped=0
file scripts/tests/update-ecs-runner-qwen-workflow.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/update-ecs-runner-qwen-workflow.test.js


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  scripts/tests/update-ecs-runner-qwen-workflow.test.js: PPPPP

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

--- per-invocation detail (full copy in the artifact) ---
round 1 · scripts/tests/update-ecs-runner-qwen-workflow.test.js: P (exit 0)
round 2 · scripts/tests/update-ecs-runner-qwen-workflow.test.js: P (exit 0)
round 3 · scripts/tests/update-ecs-runner-qwen-workflow.test.js: P (exit 0)
round 4 · scripts/tests/update-ecs-runner-qwen-workflow.test.js: P (exit 0)
round 5 · scripts/tests/update-ecs-runner-qwen-workflow.test.js: P (exit 0)

Evidence images

01-ab-pool-replay-base-vs-head

02-mutation-matrix

03-verify-sibling-sweep-absent-door

04-size-gate-blocker-base-vs-head

05-trash-cleanup-sudoers-models-diverge

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

Qwen Code · sandboxed verification

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

Requesting changes on two specific items at head 9b3b6643 — neither is a design objection, and both have a clear next step. Full reasoning is in the stage comments above. @yiliang114

1. Blocking — this commit crosses the workflow size ratchet and reds a required lane.

Lint & Static (ubuntu-latest, Node 22.x) fails on this head, and the annotation names this PR's own file:

.github/workflows/update-ecs-runner-qwen.yml grew to 12618 bytes, 5431 over its recorded 7187 (allowance 4096). Move prose into a sibling .md and long steps into .github/scripts/ — or, if the growth is real, update .github/workflows/.size-baseline in this PR and say why.

Verified against the checked-in baseline rather than taken from the annotation: .size-baseline:63 records 7187 update-ecs-runner-qwen.yml, the allowance in check-workflow-size.sh is 4096, so the threshold is 11283 — and the file fetched raw at this head SHA is 12618 bytes. This PR changes two files and .size-baseline is not one of them. The script's file_matches_base downgrade does not apply, because it only rescues a byte-identical copy and this PR genuinely edits the file. Merging this reds main.

This is new at exactly this commit: at 6aab6c577 the file was 11262 bytes, passing by 21. The growth that review round 6 recorded as a deferred Suggestion is now a hard failure. It is also not infra noise — of 12 open PRs sampled, six have this lane green and the one other red (#11117) carries no ratchet annotation.

Your earlier CI-attribution comment was correct and I confirmed its substance, but it described head 6aab6c577, where this lane was red for the main-side check:no-webui reason. On this head that cause is gone and a real one replaced it — the other two lanes (Test cancelled at the 2h job max, web-shell E2E Smoke) are still infra and are red across several unrelated PRs, but this one is now the diff.

Either remedy the gate names works. Moving the install-mode prose into a sibling .md is the better one — the reasoning is genuinely worth keeping and a sibling doc keeps it without spending the ratchet. Bumping .size-baseline here with a one-line reason is the faster one and is explicitly allowed.

2. The dispatch oracle review round 6 set as a merge precondition was never run.

R1-2 required an all-five-pools-green dispatch before merging the update-path change. There has been no workflow_dispatch of this workflow at any head after c64936d6f6 (09-03 12:18Z). The last one, run 33757297024 at 16b7737318, left hk-1 and hk-2 failed with hk-3/4/5 green — and that head carried the probe-based shape, not the ordered mode 2 this PR now ships. Mode 2 exists solely to save those two pools and has never been observed doing it.

To be clear about what is settled: the new commit implements R1-2's prescribed option (1) exactly, and its first condition is met — the command-specific stub now models the observed sudoers spec instead of accepting any argv containing npm install -g, and the tests assert which user npm ran as, that exactly one install happened, and the absence of --prefix. That is a real pin, and it is what makes the earlier green suite meaningful. But a replay is still a model of the fleet, and the reason R1-2 asked for a dispatch is that the previous model was wrong in exactly the way that mattered.

Neither sandboxed lane can reach the production ECS pools, so this one needs a real dispatch. @qwen-code /verify would separately settle whether the replay tests pin the mode-selection logic — A/B with mode 2 removed, and with --prefix /usr/local wedged back into install_qwen_named_spec.

Not blocking, and deliberately not held against this PR: the || chain cannot distinguish "sudo rejected the argv" from "npm failed under sudo", so on hk-4/hk-5 a transient npm failure falls through to mode 2's unpinned argv and installs into root's custom Node prefix. It fails closed at Verify, so the fleet is never silently stale; it is already in round 6's deferred list, and at round 7 this repo's guidance is to land only Critical fixes. Same for the title/body mismatch (still "60-minute budget", still "Nothing else changes" while the diff ships 90 minutes and reworks install/verify) — please sync it before the squash, but it is hygiene, not a reason to hold this back.

Fix the ratchet and run the dispatch, and I would not be surprised to approve the next pass.

中文说明

在 head 9b3b6643 上就两项具体问题请求修改——两者都不是对设计的异议,且都有明确的下一步。完整推理见上方的 stage 评论。@yiliang114

1. 阻断——本提交越过了工作流体积棘轮,把必需检查搞红了。

Lint & Static (ubuntu-latest, Node 22.x) 在此 head 失败,注解点名的正是本 PR 自己的文件:.github/workflows/update-ecs-runner-qwen.yml 增长到 12618 字节,超出记录的 7187 共 5431 字节(允许 4096)。

我没有采信注解,而是对照仓库内基线核实:.size-baseline:63 记录 7187 update-ecs-runner-qwen.ymlcheck-workflow-size.sh 的允许量是 4096,阈值即 11283——而在 head SHA 上取到的原始文件是 12618 字节。本 PR 只改两个文件,.size-baseline 不在其中。脚本的 file_matches_base 降级不适用,因为它只救逐字节相同的副本,而本 PR 确实修改了该文件。合并会把 main 搞红。

这恰好在本提交才出现:在 6aab6c577 上文件是 11262 字节,以 21 字节之差通过。评审第 6 轮记为延后 Suggestion 的体积增长,如今成了硬失败。它也不是基础设施噪声——抽查的 12 个开放 PR 中 6 个该检查为绿,另一个红的(#11117)没有棘轮注解。

你此前的 CI 归因评论是正确的,我核实了其实质,但它描述的是 head 6aab6c577,当时该检查红的原因是 main 侧的 check:no-webui。在此 head 上那个原因已消失,取而代之的是一个真实原因——另外两条(Test 在 2 小时 job 上限被取消、web-shell E2E Smoke)仍是基础设施问题,在多个无关 PR 上同样红,但这一条现在属于本 diff。

门禁给出的两种补救都可行。把安装模式的说明移到同级 .md 是更好的一种——这些推理确实值得保留,同级文档既能保留又不消耗棘轮额度;在本 PR 内更新 .size-baseline 并附一行理由是更快的一种,也是明确允许的。

2. 评审第 6 轮设定为合并前置条件的 dispatch 验证从未执行。

R1-2 要求在合并 update-path 改动前完成一次五池全绿的 dispatch。自 c64936d6f6(09-03 12:18Z)之后,本工作流在任何 head 上都没有 workflow_dispatch 记录。最近一次是 16b7737318 的 run 33757297024,hk-1 与 hk-2 失败、hk-3/4/5 绿——而那个 head 用的是探针式结构,不是本 PR 现在交付的有序模式 2。模式 2 存在的唯一目的就是拯救这两个池,却从未被观测到做到过。

需要说清楚哪些已经确定:新提交精确实现了 R1-2 指定的第 (1) 种方案,且其第一个条件已满足——command-specific 桩现在建模实测的 sudoers 规格,而不再接受任何含 npm install -g 的 argv,测试断言 npm 以哪个用户运行、只发生一次安装、且不含 --prefix。这是真正的钉子,也正是它让此前的绿色套件有了意义。但重放终究只是机群的模型,而 R1-2 之所以要求 dispatch,正因为上一个模型在最关键的地方是错的。

两个沙箱通道都到不了生产 ECS 机群,所以这一项需要真实 dispatch。@qwen-code /verify 可以单独验证重放测试是否钉住了模式选择逻辑——分别在移除模式 2、以及把 --prefix /usr/local 塞回 install_qwen_named_spec 两种变异下做 A/B。

非阻断,且刻意不作为拦住本 PR 的理由|| 链无法区分"sudo 拒绝该 argv"与"npm 在 sudo 下失败",因此在 hk-4/hk-5 上一次瞬时 npm 失败会落到模式 2 不含 pin 的 argv,装进 root 的自定义 Node 前缀。它在 Verify 处失败是收敛的,机群不会被悄悄留在旧版;这一条已在第 6 轮的延后清单中,而在第 7 轮,本仓库的指引是只落 Critical 修复。标题/正文不一致同理(仍写"60 分钟"、仍写"其他不变",而 diff 实际交付 90 分钟并重构了 install/verify)——请在 squash 前同步,但那是规范性问题,不是拦住本 PR 的理由。

修掉棘轮、跑一次 dispatch,下一轮我批准它也不会意外。

Qwen Code · qwen3.8-max-2026-09-02

yiliang114 and others added 2 commits September 6, 2026 02:31
check-workflow-size.sh red-walled the lint lane on this PR: the file
grew to 12618 bytes, 5431 over its recorded 7187 and past the 4096
allowance. The growth is real rather than prose that belongs in a
sibling .md -- it is the inline rationale for the three install modes
(pinned sudo for hk-3/4/5, the sudoers-named argv for hk-1/hk-2, runner
user where no passwordless sudo exists) with the two run IDs that
distinguish them, plus the measured v0.23.0 registry-propagation gap
behind RESOLVE_TIMEOUT_SECONDS 5400. Each comment sits next to the
shell it explains, so moving them out would separate the evidence from
the code it justifies.

Verified locally: check-workflow-size.sh exits 0 with
WORKFLOW_SIZE_BASE_SHA set to this PR's base, and the vitest mirror
scripts/tests/workflow-size.test.js passes 202/202.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-conflict/jmtooas8sln
The web-shell E2E lane runs `npx vitest run --root ./integration-tests
./chat-transcript-document.test.ts`, a gate main added after this branch
diverged. CI checks out the PR head but evaluates the merged workflow, so
the step ran against a tree without that file and vitest exited 1 with
"No test files found". Catching up with main brings the gated file in.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-conflict/jmtosl3ualt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants