fix(ci): route release pipeline Linux jobs to the ECS runner pool - #10036
Conversation
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Thanks @yiliang114 — the diff itself looks straightforward: the runs-on expression matches exactly the ECS routing pattern the other lanes already use (qwen-code-pr-review.yml, stale.yml, …), with the same MAINTAINER_ECS_RUNNER_DISABLED kill switch and hosted fallback.
Before it can move forward, the PR body needs to follow the PR template — two required sections are missing:
## Risk & Scope— this one matters for this PR specifically: it moves the releasepublishjob, which carriessecrets.CI_BOT_PATand mints npm OIDC provenance tokens (id-token: write+npm publish --provenance), onto the shared persistent ECS pool. Please spell out the tradeoff there: main risk, what's not validated (per your note, no live release run yet), and breaking changes (none expected — the kill switch restores hosted behavior without a code change).## Linked Issues— link whatever tracks the v0.22.1 queue congestion, or write "N/A".
The template's bilingual <details> Chinese summary is also missing.
Once the body is updated, re-trigger with an @qwen-code /triage comment and the review continues.
中文说明
感谢 @yiliang114 —— diff 本身看起来很直接:runs-on 表达式与其他 lane(qwen-code-pr-review.yml、stale.yml 等)已在使用的 ECS 路由模式完全一致,同样带 MAINTAINER_ECS_RUNNER_DISABLED kill switch 和 hosted 回退。
但在继续之前,PR 描述需要遵循 PR 模板 —— 缺少两个必需章节:
## Risk & Scope—— 这一节对本 PR 尤其重要:它把携带secrets.CI_BOT_PAT、并通过 OIDC 签发 npm provenance token(id-token: write+npm publish --provenance)的 releasepublishjob 移到了共享的持久化 ECS 池。请在那里写明权衡:主要风险、未验证项(按你的说明,尚无真实 release 运行)、破坏性变更(预期没有 —— kill switch 可无需改代码恢复 hosted 行为)。## Linked Issues—— 关联跟踪 v0.22.1 排队拥堵的 issue,或写 "N/A"。
同时缺少模板中的 <details> 中文说明。
补全描述后,评论 @qwen-code /triage 重新触发,审查会继续。
— Qwen Code · qwen3.8-max
|
@qwen-code /triage |
|
Sandboxed verification: ❌ not passed — findings reported (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: 111 passed · 0 failed · 111 total Flakiness gate: not applicable — no runnable changed test files (0 out-of-scope file(s) noted in the log) 中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:111 通过 · 0 失败 · 111 总计 抖动门:不适用 — no runnable changed test files (0 out-of-scope file(s) noted in the log) Verification reportPR #10036 Deep Verification —
|
| cell | oracle | base (a6d30eb) |
head (79433e5) |
|---|---|---|---|
repo=QwenLM/qwen-code, switch unset |
resolved runs-on |
ubuntu-latest |
["self-hosted","linux","x64","ecs-qwen"] |
repo=QwenLM/qwen-code, switch = 'true' |
resolved runs-on |
ubuntu-latest |
["ubuntu-latest"] |
repo=<fork> |
resolved runs-on |
ubuntu-latest |
["ubuntu-latest"] |
full 32-cell table, both == modes |
head ≡ reference; head ≡ documented expectation | n/a | 64/64 |
| six jobs mutually identical | string identity | n/a | 6/6 |
| base never routes to ECS | base runs-on literal |
6/6 ubuntu-latest |
n/a |
Witness: evidence/01-expression-ab-head-vs-base.png (truth table, string identity, flip cell, 76 PASS lines).
Structure A/B (base vs head, parsed with js-yaml): job sets identical (7 jobs); after deleting the six runs-on keys the two documents are deep-equal — nothing else changed (comments are invisible to the parser); the diff audit confirms every removed line is runs-on: 'ubuntu-latest' (6) and every added line is a comment or the new runs-on (24); exactly one file touched; audio_capture_prebuilds keeps its reusable-workflow call with no runs-on and audio-capture-prebuilds.yml keeps its own platform matrix with no ecs-qwen (0 diffs). Witness: evidence/02-structure-ab-job-inventory.png (21 PASS lines).
Findings
1. High — migrated checkout jobs lack the pool-wide Restore workspace ownership step
Every workflow that already consumes the ECS pool implements this step before checkout: ci.yml, qwen-autofix.yml, qwen-code-pr-review.yml, qwen-triage.yml, sdk-java.yml, serve-ab.yml, serve-ab-publish.yml (7/7). release.yml has zero occurrences, and this PR moves its five checkout-bearing jobs (prepare, quality, integration_none, integration_docker, publish; notify_failure has no checkout) onto that pool.
The mechanism is documented by the repo itself (ci.yml, test job): "a previous containerised job (e.g. qwen-triage verify) may leave root-owned, read-only files anywhere in the workspace. Restore ownership and write permission unconditionally so the checkout below can wipe the tree without EACCES." Self-hosted runners reuse workspaces (this image: persistent /__w, 85% used); the base side of this A/B is hosted ubuntu-latest, which is ephemeral and cannot be poisoned — so the hazard is introduced precisely by this migration. Failure scenario: a nightly/scheduled release job lands on a runner whose workspace holds root-owned leftovers → actions/checkout fails with EACCES → prepare fails → the whole pipeline blocks, i.e. the exact stall this PR exists to remove. notify_failure runs on always(), so the failure is loud, and the kill switch restores hosted — but the outage lasts until someone flips it.
Reproduce the gap:
grep -rl 'Restore workspace ownership' .github/workflows/ # 7 files, release.yml absent
grep -c "name: 'Checkout'" .github/workflows/release.yml # 5 exposed jobsMeasured suggested fix (preserves commit intent; scratch copy only): inject the ci.yml integration_cli step verbatim before each of the five Checkout steps — tmp/.../logs/release-head-with-ownership-fix.yml. Scripted results: step inserted at 5/5 sites, file parses, first step of each job is Restore workspace ownership, actionlint clean, yamllint clean (F0–F4 in evidence/04-final-checks-metadata-gates-fix.png). Honest bound: I could not reproduce the poisoned-workspace failure end-to-end from inside this container (it needs a real runner alternating a root-container job → release job), and this lane's container runs as uid 1000, so the active poisoning sources are container/docker-sandbox jobs running as root rather than this lane's own writes. The finding stands on the repo's own convention and documented mechanism; the fix is the convention itself, not new invention.
2. Low — risk section understates which jobs carry secrets onto the shared pool
The description names only publish (CI_BOT_PAT + npm OIDC id-token). The three test jobs also inject secrets.OPENAI_API_KEY/OPENAI_BASE_URL/OPENAI_MODEL onto the shared persistent pool (lines 161–163, 226–228, 280–282 of release.yml). This is an extension of an existing pattern, not a new hazard: ci.yml's integration_cli already carries the same secrets on ECS-routed runners. Suggest adding one sentence to the Risk section so the tradeoff list is complete; no code change needed. publish itself keeps its environment: production-release protection and concurrency serialization on the move — both unaffected by runner label.
3. Informational — kill-switch string equality case handling
If the runner's == is case-insensitive, MAINTAINER_ECS_RUNNER_DISABLED=True also disables ECS; if case-sensitive, only exact lowercase true does. Either way head and the deployed reference agree in all 32 cells (the expressions are byte-identical), so nothing here is PR-specific. Operators should set exactly true.
4. Informational — no zstd in this lane image
command -v zstd → MISSING (also docker, sudo absent in-container; node v22.23.2, npm 10.9.8, uid 1000). For cache: 'npm' this can change actions/cache archive identification between hosted and ECS, but ci.yml's ECS-routed jobs already build the same cache entries with cache-dependency-path: package-lock.json, so worst case is one cold cache on a first ECS release run. Release jobs use actions/setup-node@v6, which provisions Node itself — host PATH Node is irrelevant (the sdk-java.yml warning does not apply here).
Not covered
- Live runner selection and publishing on ECS — requires an actual release dispatch (the PR's own step 3 acknowledges this); the
ecs-qwenlabel set's existence is not asserted here because the repo's actionlint flags ignorelabel ... is unknown; it is established indirectly by the lanes already running on it. - Poisoned-workspace EACCES end-to-end — reproduced the hazard's documented shape and the convention gap, not the trigger (needs real runner alternation; see Finding 1's bound).
audio-capture-prebuilds.ymlinternals — verified untouched (0 diffs) and matrix preserved; its own runners were not in scope.- Repo-wide test/typecheck gates — workflow-only diff on a file whose base is clean in CI; nothing to run.
- Per-commit attribution — single commit; local reachability matches the metadata snapshot (M1–M3), shallow checkout noted; aggregate diff = the commit.
- Base staleness — checked: live
main== base tip (a6d30eb…), so no trial merge was needed.
Methodology
One CI verify container (node:22-bookworm, uid 1000, persistent /__w) on the lane's own runtime, merge-ref checkout (HEAD merge commit, HEAD^1 base, HEAD^2 PR head). Harnesses are mock-free against their unit: the changed YAML itself, evaluated by (a) a GitHub-expression-semantics evaluator with expressions extracted from parsed YAML of release.yml and qwen-code-pr-review.yml, (b) js-yaml deep structural comparison of git show HEAD^1: vs working tree, and (c) the repo's pinned linters — actionlint 1.7.12 and yamllint 1.35.1 installed via scripts/lint.js --setup / PyPI wheels, run with the repo's exact flags and .yamllint.yml. Both gates were proven live with planted violations before their clean results were cited. Runtime facts (node -v, command -v docker/zstd/sudo, id -u, /__w mount) were measured directly in-container per the environment contract. Raw logs: logs/ (01-expression-ab.log, 02-structure-ab.log, 04-final-checks.log, gates/), harness sources 01-expression-ab.mjs, 02-structure-ab.mjs, 03-gates.sh, 04-final-checks.mjs; evidence images in evidence/.
Flakiness gate log
verdict: n/a
summary: no runnable changed test files (0 out-of-scope file(s) noted in the log)
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
|
Gate re-run at the same head
Moving on to code review. 🔍 中文说明在同一 head
进入代码审查。🔍 — Qwen Code · qwen3.8-max Reviewed at |
|
Code review re-checked at Routing and credential boundary — verified at this head.
No new findings. The round-16 Independent real-environment verification by the maintainer. Since the last pass, @wenshao built a real verification environment and reported on it (report) — this is his evidence, not mine, and it is substantially stronger than anything a static pass can produce: three GitHub Actions runs on his fork proving the YAML anchor resolves on real runners, the hosted fallback resolves to Testing evidence. Unattended run — PR code is never built or executed here; this section quotes the PR's own CI at
中文说明在 路由与凭证边界——已在本 head 验证:
无新发现。 本 head 的第 16 轮 维护者的独立真实环境验证。 上次评审之后,@wenshao 搭建了真实验证环境并出具报告(见上方链接)——这是他的证据,不是我的,而且比静态评审能产出的任何证据都扎实:在其 fork 上的三次 GitHub Actions 真实运行,证明 YAML 锚点在真实 runner 上可解析、hosted 兜腿解析为 测试证据。 无人值守运行——本流程从不构建或执行 PR 代码;本节引用本 head 的 PR 自身 CI(run 33222648576),本轮经 API 重新获取且无变化——同一 head,同一全绿。必选车道 (CI 明细见上方表格,此处不重复。) — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — code and CI are where they need to be, and the one thing this PR was waiting on has happened: the maintainer decision the last run deferred to @wenshao for was made, in writing, at this exact head. The remaining items are recorded follow-ups, not merge blockers. Stepping back: the defer was binary — isolate the release lanes, or explicitly risk-accept the residual shared-pool exposure. @wenshao answered with action rather than words: an independent real-environment verification (three real GitHub Actions runs, an 18-case hostile-geometry matrix against the byte-identical wipe step run as an unprivileged uid, 22 mutation probes, static and supply-chain checks), a written report that documents the residual boundary himself, an approving review pinned to this head, and a re-run request here. That is the merge-gate decision the PR's own Risk & Scope names — explicit maintainer acceptance with the residual documented — made by the maintainer the defer addressed. What I re-verified on my side before acting on it: the head hasn't moved ( Recorded follow-ups — none blocking merge per the approving maintainer, but none of them should be silently dropped either:
One housekeeping note: the round-16 Approving, pinned to the reviewed commit. ✅ 中文说明置信度:4/5 —— 代码与 CI 都已到位,而本 PR 在等的唯一一件事已经发生:上次转交给 @wenshao 的维护者决定,已以书面形式在本 head 上作出。剩余事项是备案跟进项,不是合并阻断。 退一步看:转交的问题是二选一——隔离发布车道,或明确接受共享池残余风险。@wenshao 用行动而非言语作答:独立真实环境验证(三次真实 GitHub Actions 运行、对字节一致的清场步骤以非特权 uid 跑的 18 格敌对几何矩阵、22 个变异探针、静态与供应链检查),一份亲自记录残余边界的书面报告,一枚钉在本 head 上的批准评审,以及这里的重新触发。这正是 PR 自身 Risk & Scope 点名的合并门禁决定——书面记录残余后的明确维护者接受——由转交对象本人作出。 行动前我这一侧复核了:head 未动( 备案跟进项——按批准维护者的意见均不阻断合并,但任何一项都不应被悄悄丢弃:
一件内务备注:第 16 轮 批准,钉在所审提交上。✅ — Qwen Code · qwen3.8-max Reviewed at |
|
Triage re-run completed without a new review. The bot already has a review of its own on 机器人在 The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
@qwen-code /triage |
|
Sandboxed verification: ❌ not passed — findings reported (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: 154 passed · 1 failed · 155 total Flakiness gate: ✅ 1 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:154 通过 · 1 失败 · 155 总计 抖动门:✅ 1 changed test file(s) x 5 identical rounds, no divergence Verification reportPR #10036 Deep Verification (follow-up round) —
|
| # | Finding | Sev | Status at new head 225fbf57 |
|---|---|---|---|
| 1 | Migrated checkout jobs lack the pool-wide Restore workspace ownership step |
High | Fixed. Commit 2 adds the step to all five checkout jobs (prepare, quality, integration_none, integration_docker, publish); notify_failure correctly excluded (no checkout). The run: payload is byte-identical to the convention deployed in ci.yml and byte-identical across the five jobs (assertions in harness 02); unconditional, steps[0] immediately before Checkout; pinned by the new test (vacuity matrix M1–M3 below); live-executed in this container (harness 04: happy path + checkout-wipe oracle, fail-soft on unowned leftovers, root-skip branch — 11 assertions). |
| 2 | Risk section names only publish secrets; the three test jobs also carry OPENAI_API_KEY onto the pool |
Low | Stands. The current body snapshot still names only publish (re-measured against $QWEN_VERIFY_CONTEXT); no description update in round 2. See Finding 2. |
| 3 | Kill-switch == case-handling informational |
Info | Stands. The expression is unchanged and byte-identical to the deployed reference, so head ≡ reference in all 32 cells under both string-equality semantics; nothing PR-specific. |
| 4 | Lane-image facts (no zstd/docker/sudo; setup-node provisions Node) |
Info | Stands, re-measured. zstd/docker/sudo still absent, node v22.23.2, npm 10.9.8, uid 1000; the five jobs still use setup-node@v6.4.0 with cache: 'npm' (deep-equal A/B proves those steps unchanged). |
Central claim + A/B (re-measured at the new head)
Central claim: the six Linux release jobs resolve to ["self-hosted","linux","x64","ecs-qwen"] exactly when github.repository == 'QwenLM/qwen-code' and vars.MAINTAINER_ECS_RUNNER_DISABLED != 'true', fall back to ubuntu-latest otherwise — identical to qwen-code-pr-review.yml. Commit-2 claim: every checkout in release.yml is preceded by the pool-convention ownership step.
| cell | oracle | base (a6d30eb) |
head (225fbf57) |
|---|---|---|---|
repo=QwenLM/qwen-code, switch unset |
resolved runs-on |
ubuntu-latest |
["self-hosted","linux","x64","ecs-qwen"] |
repo=QwenLM/qwen-code, switch = 'true' |
resolved runs-on |
ubuntu-latest |
["ubuntu-latest"] |
repo=<fork> |
resolved runs-on |
ubuntu-latest |
["ubuntu-latest"] |
full 32-cell table, both == modes |
head ≡ reference; head ≡ documented expectation | n/a | 64/64 |
| six jobs mutually identical to reference | string identity | n/a | 6/6 |
| base never routes to ECS | base runs-on literal |
6/6 ubuntu-latest |
n/a |
| structure: delta = exactly {6 runs-on, 5 ownership steps} | deep-equal after stripping both classes | base | head ✓ |
| ownership payload ≡ convention | byte identity vs ci.yml + 7-file census |
n/a | 5/5 + 7/7 files |
Witnesses: 01-expression-ab-truth-table.png, 02-structure-ab-delta-audit.png.
Corrections
To round 1's Not covered: "Repo-wide test/typecheck gates — workflow-only diff … nothing to run" was wrong. scripts/tests/workflow-size.test.js (run by npm run test:scripts, which the release quality job itself executes via test:release) gates the size of release.yml and is part of the changed surface once commit 2 grew the file. Round 1's diff stayed inside the 4096-byte allowance, which is why it passed unseen; this round runs the suite and finds the ratchet red (Finding 1).
Findings
1. High — commit 2's ownership steps breach the workflow size growth ratchet; CI fails until .size-baseline is bumped in this PR
Measured numbers:
| quantity | bytes |
|---|---|
recorded baseline (.github/workflows/.size-baseline) |
39291 |
base release.yml (HEAD^1) |
39295 (+4 pre-existing drift, within allowance) |
head release.yml (HEAD^2) |
45677 |
allowance (check-workflow-size.sh) |
4096 → limit 43387 |
| overage over limit / over recorded | 2290 / 6386 |
Both repo enforcers fail at head, proven live:
- vitest mirror (
workflow-size.test.js, collected bytest:scripts→ runs in the releasequalityjob and inci.yml'stest:cilane): ".github/workflows/release.yml is 6386 bytes over its recorded 39291 and differs from the PR's base — … or, if the growth is real, update .size-baseline in this PR and say why". - shell gate, run against the real base SHA: exit 1 with
::error file=.github/workflows/release.yml::… grew to 45677 bytes, 6386 over its recorded 39291 (allowance 4096) ….
Witness: 06-size-ratchet-blocker-and-measured-fix.png. Blast radius: every lane that runs test:scripts or the Check workflow file size step fails on this file — including this PR's own CI (ci.yml's three npm run test:ci lanes end in test:scripts) and, after merge, every release pipeline quality job, which would block the very releases this PR exists to un-stall. Attribution is clean: base sits within allowance (R4), so the overage was introduced by this PR. Measured per-commit growth: commit 1 added 2267 bytes net (within the 4096 allowance — which is why round 1 passed unseen); commit 2's five ownership steps added 4115 bytes net, taking the cumulative growth to 6382 and across the limit. The ratchet is working as designed; the PR simply did not bump the baseline.
Measured suggested fix (preserves commit intent)
One line, in this PR, plus one sentence in the description saying why (the remedy the gate itself prescribes):
--- a/.github/workflows/.size-baseline
+++ b/.github/workflows/.size-baseline
-39291 release.yml
+45677 release.ymlVerified in a scratch copy of head: with the bump, the entire workflow-size.test.js suite is green (195/195, exit 0) and the shell gate prints ✅ every workflow file is under the 470000-byte gate and within 4096 bytes of its recorded baseline (exit 0). Collateral check: the sed is anchored to the exact full line, no other baseline entry moves. The alternative the gate offers — moving the steps into .github/scripts/ — would diverge from the pool-wide convention that seven other workflows carry inline, so the bump is the convention-consistent fix.
2. Low (carried over, stands) — risk section still understates which jobs carry secrets onto the shared pool
The description names only publish (CI_BOT_PAT + npm OIDC id-token). The three test jobs (quality, integration_none, integration_docker) also inject OPENAI_API_KEY/OPENAI_BASE_URL/OPENAI_MODEL onto the shared persistent pool. This remains an extension of an existing pattern (ci.yml already carries the same secrets on ECS-routed runners), so no code change — one sentence in the Risk section completes the tradeoff list. Declined-by-silence in round 2; I agree it is low priority, but it should be acknowledged rather than silently dropped.
Informational notes (no action required)
- The new test pins presence, order, and count — not payload content. The vacuity matrix (below) proves it catches removal (M1), reordering after checkout (M2), and an unmatched sixth checkout (M3), each killing exactly that test with no collateral. But a future edit that changed the chown/chmod semantics while keeping the step name would pass; its siblings in the same file pin exact text. Today the payload is protected by byte-identity with the seven deployed workflows — a weaker pin than the file's local convention.
Clean stale .qwen before checkoutis aci.yml-only addition (3 occurrences there, 0 in the other six pool consumers), so ownership-only is the dominant convention andrelease.ymlmatches it. The ownership step's sudo-less edge degrades to two explicit::warning::lines and a loud checkout failure (proven live, harness 04 cell H2), which is the accepted behavior of the deployed payload.- Host tooling:
gh, used bypublishandnotify_failure, is already proven on the ECS hosts — nine host-level ECS jobs acrossqwen-triage.yml,qwen-autofix.yml, andqwen-code-pr-review.ymluse it. - Environmental, not PR-attributed:
install-script.test.jsfails in this container because thezipbinary is missing — isolated by a three-part chain (harness 06 R7): the PR touches none of its subjects, the throw is the host-tooling precondition line, and the file is byte-identical between base and head, so base fails identically. The G0 runs also surfaced one vitest[vitest-worker]: Timeout calling "onTaskUpdate"unhandled error, a load-dependent shape (the vitest config comments document it for slower runners; this lane is a shared, loaded runner).
Vacuity matrix for the new test (scratch-root copy of head)
| arm | result |
|---|---|
| control (unmutated head copy) | green, 18/18 in file |
| M1: revert commit-2 hunks (all five steps removed) | red — exactly 1 failing test: the new one |
M2: prepare's ownership step moved after its checkout |
red — exactly 1 failing test: the new one |
M3: stray sixth Checkout with no ownership step |
red — exactly 1 failing test: the new one |
Witness: 05-test-gate-and-vacuity-matrix.png. Positive control landed in the same file as the mutants (the M-arms red, control green, all under the same vitest invocation).
Not covered
- Live runner selection and publishing on ECS — requires an actual release dispatch (the PR's own plan step 3 says so); the
ecs-qwenlabel set's existence remains indirectly established by the lanes already running on it (the repo's actionlint flags ignore unknown labels). - Poisoned-workspace EACCES end-to-end — reproduced the script's behavior branches live within uid-1000 limits (owned-but-locked restore + wipe oracle; unowned fail-soft with no side effects; root-skip branch), but root-owned leftovers cannot be created here without sudo, and the real trigger needs runner alternation. The sudo-success branch stands on byte-identity with the payload already running in seven workflows.
- Per-commit attribution — depth-2 checkout:
git rev-list HEAD^1..HEAD^2sees 1 commit, the snapshot lists 2, and the repo is shallow; the aggregateHEAD^1..HEADdiff was verified instead (exactly two files; the test file is additions-only). - Repo-wide gates beyond the affected surface —
test:scriptsran in full (1634 tests); prettier on the two changed files proven clean with a live control; repo-wide eslint/shellcheck/typecheck not re-run for a diff of one workflow + one test file. audio-capture-prebuilds.ymlinternals — verified byte-identical base vs head (matrix untouched); its own runners were out of scope.
Methodology
One CI verify container (node:22-bookworm, uid 1000, persistent /__w, no pip3/zip/zstd/sudo/docker) on the lane's own runtime, merge-ref checkout (HEAD merge commit, HEAD^1 base, HEAD^2 PR head). Harnesses are mock-free against their unit: expressions extracted from parsed YAML and evaluated by a GitHub-semantics evaluator over a 32-cell truth table (both == modes); js-yaml deep structural comparison classifying every base→head delta; the repo's pinned linters — actionlint 1.7.12 via scripts/lint.js --setup, yamllint 1.35.1 installed as PyPI wheels (pip3 is unavailable here) and invoked as python3 -m yamllint after proving that -m yamllint.cli silently exits 0 (no __main__ guard) — each run with the repo's exact flags and proven live with planted violations; the ownership-step payload extracted verbatim from parsed YAML and executed under bash --noprofile --norc with real workspace fixtures; the new test's vacuity matrix run in a scratch root (git archive HEAD + shared node_modules symlink) against three text-surgery mutants; the size ratchet proven at both enforcers and its one-line fix verified in the scratch root. Raw logs in logs/ (01–07 + per-cell vitest/gate outputs), harness sources alongside, evidence images in evidence/.
Flakiness gate log
rounds=5 files=1 skipped=0
file scripts/tests/release-workflow.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/release-workflow.test.js
per-file results (P=pass F=fail I=infra-exit, one letter per run):
scripts/tests/release-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/release-workflow.test.js: P (exit 0)
round 2 · scripts/tests/release-workflow.test.js: P (exit 0)
round 3 · scripts/tests/release-workflow.test.js: P (exit 0)
round 4 · scripts/tests/release-workflow.test.js: P (exit 0)
round 5 · scripts/tests/release-workflow.test.js: P (exit 0)
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
The ECS routing and workspace ownership guards intentionally grow release.yml beyond the workflow-size ratchet allowance. Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max via Qwen Code /review (v0.22.0)
Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Disable redundant setup-node cache transfers on the persistent ECS pool and add explicit timeouts for the remaining shared-pool jobs. Pin both contracts in the release workflow test.\n\nCo-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: reverse audit — stopped before round 4 by the review time budget.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:反向审计——评审时间预算不足,未能开始第 4 轮。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max via Qwen Code /review (v0.22.0)
…osed Port the sibling wipe guards (realpath canonicalization, symlink heal, root denylist, runner-workspace containment) onto all five release wipe copies, remove planted user-level state the workspace wipe cannot see (~/.npmrc script-shell, global git exec keys) with the qwen-autofix pre-checkout denylist, and make the docker preflight print docker's own error output before failing closed. Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
Replace substring tripwires with full-string equality against the shared canonical wipe constant and require the step to stay first, so a commented-out find, an inserted early exit, or a uniformly dropped ownership ladder fails review instead of shipping green. Pin the docker preflight's full fail-closed form (captured daemon output printed, exit 1) so deleting the exit or inverting the guard fails the suite. Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
Restore the type-annotation formatting from origin/main so this PR contains zero non-CI hunks. CI's prettier step is write-only (prettier --write ., no drift check), same as main carries today, so this revert is CI-neutral. Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
6 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- stale .size-baseline entry for release.yml (58255 recorded vs 53022 actual) — already reported as R12-3 in the round-12 deferred list (review 5049354051; rounds 6-7 lineage)
- wipe refusal branches (denylist / empty-RWS / heal refusal) without behavioral tests — already reported in the round-9 deferred list (review 5045129895) and the round-12 deferred list (review 5049354051)
- wipe guard chain forked across workflow copies with no cross-file pin — already reported in the round-9 deferred list (review 5045129895)
- digest pin drops tag-following / no tag-digest correspondence check — already reported in the round-11 deferred list (review 5047954141)
- pre-claim queue-exhaustion watchdog for the pool-routed lanes — already reported as R2-6/R3-6 (comment 3862620744), author deferred to follow-up issue 10176
- docker-info preflight without a timeout wrapper — already reported in the round-8 deferred list (review 5043617782)
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) was skipped in CI (merge_group-gated) and its suite did not run locally on that platform; it collects the changed scripts/tests/release-workflow.test.js (YAML pins are platform-independent; the behavioral suite self-skips on win32).
Not reviewed: build-and-test — Test (macos-latest, Node 22.x) was skipped in CI (merge_group-gated) and its suite did not run locally on that platform.
Not reviewed: test-efficacy mutation probe could not run — no green baseline: pre-existing environmental failures in scripts/tests/qwen-triage-workflow.test.js (sandbox loopback-HTTP block) at both HEAD and the merge base (harnessValidated: null; unprobed scope disclosed, not a finding).
Not reviewed: the executable-script lint — Dockerfile: hadolint is not installed.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 13, not a blocker) — recorded, not requested in this round:
scripts/tests/release-workflow.test.js:951 — [review] routing tests pin runs-on strings but not the trigger set against fork-fireable trigger additions.github/workflows/release.yml:191 — [review] PR description still narrates the replaced git-config scrub and a regression test that no longer exists
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
Residual risk: this loop is persistently critical — Criticals stood in the previous round's work-list and stand again this round (1 Critical(s)), the rate of first-time findings is not falling (this round 0, previous 0), and the standing Critical backlog is not shrinking. The severity floor will not converge it. Recommendation: land-with-residual-risk — the exit is a maintainer risk-acceptance decision (merge, carrying the residual risk), not another review round. Residual-risk inventory for that decision (maintainer to complete):
| standing Critical | attack surface | attacker-dependency | blast radius |
|---|---|---|---|
| (each standing Critical) | … | … | … |
Advisory only — it does not block this review.
— qwen3.8-max via Qwen Code /review (v0.22.2)
| runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'') && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || fromJSON(''["ubuntu-latest"]'') }}' | ||
| timeout-minutes: 30 |
There was a problem hiding this comment.
[Critical] R11-1: still stands from rounds 1-2 (re-asserted in round 9, re-reported once as the class finding in round 11), re-asserted this round with two new probe-proven entrances in this PR's own isolation at head 3dd2329. The class finding remains: routing the release validation jobs onto the shared ecs-qwen pool puts them on members where concurrent/prior same-uid jobs — potentially running contributor code (ci.yml routes same-repo PR lanes onto the same pool; af-011/af-014 document several registrations per host sharing one HOME across ~27 registrations) — leave persistent state that reaches jobs carrying GITHUB_TOKEN and OPENAI_* secrets.
New this round, probe-proven at the reviewed commit:
-
The isolation block redirects git/npm/docker/gh config but NOT QWEN_HOME. integration-tests/globalSetup.ts:36 resolves the persistent ~/.qwen before its own redirect, and carryOverHostConfig() copies settings.json and oauth_creds.json wholesale into the hermetic home used by every CLI the integration suites spawn. A planted settings.json with an mcpServers entry — a schema-supported stdio command the CLI spawns at startup, merged from user scope with no approval gate — reaches integration_none/integration_docker with OPENAI_API_KEY, OPENAI_BASE_URL and OPENAI_MODEL in the job env: code execution and false-green validation gates on the lanes that gate publishing. Every sibling pool workflow isolates QWEN_HOME (qwen-autofix.yml, repo-hygiene.yml, qwen-triage.yml, ci.yml, qwen-code-pr-review.yml); release.yml alone does not.
-
The wipe's containment validation is check-then-use: geometry is validated once, then chown -R/chmod -R run (a measured ~1.1-1.3 s window on a 120k-file leftover tree) before the destructive find. A concurrent same-uid relinker — the pool shape af-011/af-014 document — swapping an intermediate path component inside that window redirected the recursive deletion in 9 of 9 in-window probe rounds: the sibling registration's checkout was deleted, the real workspace was never wiped, and the step exited 0, so the release proceeds silently against surviving planted state. find -P protects only a symlinked starting point, not intermediate components.
Two low-confidence entrances also fold into this class: docker build-cache priming survives the digest pin (probe-measured BuildKit reuse of primed RUN layers without re-execution; pre-COPY layers are primeable across commits), and the redirect targets created in same-uid-writable RUNNER_TEMP are themselves plantable by a concurrent job.
Witness:
QWEN_HOME probe (extracted step under the production wrapper, then real globalSetup under tsx):
QWEN_HOME set by step? false; planted ~/.qwen/settings.json survives wipe? true
settings.json carried into hermetic home? true
carried content: {"mcpServers":{"evil":{"command":"/tmp/attacker-binary","args":["--steal"]}}}
flip (pin QWEN_HOME to a fresh dir): carried? false
TOCTOU relink probe (unmodified script, concurrent same-uid relinker):
9/9 in-window swaps escaped — wipe_exit=0 victim_files_left=0/20 original_ws_entries_left=300
flip (re-check path components before find): 10/10 refused — wipe_exit=1
The round-12 thread records this class as pending the maintainer decision — structural closure (dedicated members / uid separation / per-job private docker daemon) or explicit risk acceptance. This round's probes bear on that decision in two ways: (a) the thread's claim that the PR's job-scoped file-state isolation is "sound for the surfaces it covers" no longer holds at this commit — both entrances above are gaps in the isolation this PR adds, not only fleet properties; (b) two tactical hardenings are available inside this PR should it proceed: export QWEN_HOME=${release_state}/qwen-home alongside the existing redirects (the pattern all five sibling pool workflows use), and re-check every path component for symlinks immediately before the find leg (a complete fix needs fd-pinned, O_NOFOLLOW-relative traversal). If either is taken, the canonicalWipe equality pin in release-workflow.test.js must gain the new lines, and removing either line from release.yml must turn that pin red.
— qwen3.8-max via Qwen Code /review (v0.22.2)
There was a problem hiding this comment.
Both new entrances acknowledged: (1) QWEN_HOME not redirected — globalSetup.ts carrying the persistent ~/.qwen/settings.json (mcpServers) into the hermetic home; (2) the TOCTOU relink window in the wipe's check-then-use containment. Neither opens a second loop: this is the same R11-1 class already under maintainer decision in the sibling thread PRRT_kwDOPB-92c6dG8KW (author reply 3879715916). The reap leg was removed entirely by 3c0ca80, and the standing ask is unchanged: (a) dedicated/drained runner members or uid separation, vs (b) explicit risk acceptance. Both entrances fall inside the same isolation boundary the ruling covers, so no further per-entrance fix will land until the maintainer rules. Thread stays unresolved by design.
There was a problem hiding this comment.
This round: no per-entrance code change, per the maintainer hold recorded on this thread (rc:3882021585 — "no further per-entrance fixes will land until the maintainer rules"). Both entrances were independently re-confirmed at code level this round: (1) the release.yml isolation block exports GIT_CONFIG_GLOBAL/NPM_CONFIG_USERCONFIG/DOCKER_CONFIG/GH_CONFIG_DIR but not QWEN_HOME, while integration-tests/globalSetup.ts resolves Storage.getGlobalQwenDir() before its own redirect and carryOverHostConfig() carries the host settings.json wholesale into the hermetic home — all five sibling pool workflows isolate QWEN_HOME, release.yml alone does not; (2) the wipe validates its geometry once, then runs the chown -R/chmod -R ladder and the destructive find against it (check-then-use). The pending maintainer decision is unchanged: (a) dedicated/drained members, uid separation, per-job docker daemon — or (b) explicit risk acceptance. Thread stays open by design until that ruling.
中文说明
本轮:按本线程记录的维护者暂缓决定(rc:3882021585 — "在维护者裁决之前不会再落地任何逐入口修复"),不做逐入口代码修改。本轮已在代码层面独立复核两个入口:(1) release.yml 的隔离块导出 GIT_CONFIG_GLOBAL/NPM_CONFIG_USERCONFIG/DOCKER_CONFIG/GH_CONFIG_DIR,但没有导出 QWEN_HOME;而 integration-tests/globalSetup.ts 在自身重定向之前就解析了 Storage.getGlobalQwenDir(),且 carryOverHostConfig() 会把宿主机的 settings.json 整体带入隔离 home — 五个同池兄弟工作流都隔离了 QWEN_HOME,唯独 release.yml 没有;(2) wipe 一次性校验几何形状后,即对该路径执行 chown -R/chmod -R 阶梯和破坏性 find(先检查后使用)。待决的维护者决策不变:(a) 专用/清空成员机、uid 隔离、按任务独立 docker 守护进程 — 或 (b) 明确风险接受。在裁决落地之前,本线程按设计保持打开。
There was a problem hiding this comment.
Position unchanged — acknowledging this repost. R11-1 remains the fleet scheduling/isolation property (same-uid co-residency on the shared ecs-qwen pool), not a defect in this PR's job-scoped file-state isolation, which the finding itself re-verifies as sound for the surfaces it covers.
Status update since the last reply: both sibling Criticals are now closed at this head — R14-1 (trailing-slash bypass) at 88eea3f and R15-1 (symlinked intermediate component) at c6bea8a. Verified at c6bea8a: scripts/tests/release-workflow.test.js passes 28/28 when run as a non-root user, and both behavioral pins turn red when their respective fix is mutation-reverted.
The pending maintainer decision is unchanged, as recorded in the prior author replies on this thread: (a) dedicated/drained release-validation members or per-registration uid isolation (plus a private per-job Docker daemon for integration_docker), vs (b) explicit risk acceptance. Per that hold, no further per-entrance code change lands until the ruling, and this thread stays open by design until then.
The reap removal (3c0ca80) shrank release.yml from 58255 to 53022 bytes after the baseline entry was written, banking 5233 bytes of unreviewed ratchet headroom inside the gate's 20000-byte slack band. Lower the entry to the true floor so future growth past the allowance is reviewed again. Gate and ratchet mirror tests stay green; the probe value actual-4097 reddens the mirror. Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
|
🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下: Autofix round summary — PR #10036 (
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
2 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- dead pool machinery pinned in hosted-only publish (D14-2) — already discussed (comment 3879855513; author ruled: leave it)
- untested fail-closed heal arms (D14-4) — already reported as R3-4 (comment 3863888365); remaining items deferred by the author to follow-up issue 10176
Not reviewed: test-efficacy — mutation probe did not run: the probe judged the changed suite unreachable (misjudged — the suite ran green 28/28), so no mutants or hunk probes were executed.
Not reviewed: the executable-script lint — Dockerfile: hadolint is not installed.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 14, not a blocker) — recorded, not requested in this round:
.github/workflows/release.yml:90 — [review] new RWS symlink refusal not backported to sibling copies.github/workflows/release.yml:157 — [review] unconditional chown/chmod double walk before the wipescripts/tests/release-workflow.test.js:452 — [review] heal-target sanitizer pinned by no testDockerfile:7 — [review] digest pin has no automated base-image refresh pathscripts/tests/release-workflow.test.js:183 — [review] trailing-slash workspace heal case untested.github/workflows/release.yml:97 — [review] empty-RWS refusal arm unpinned; deletion degrades containment to /*
Convergence: round 14 posted 2 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/release.yml (findings in round 11; 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.)
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
Residual risk: this loop is persistently critical — Criticals stood in the previous round's work-list and stand again this round (2 Critical(s)), the rate of first-time findings is not falling (this round 1, previous 0), and the standing Critical backlog is not shrinking. The severity floor will not converge it. Recommendation: land-with-residual-risk — the exit is a maintainer risk-acceptance decision (merge, carrying the residual risk), not another review round. Residual-risk inventory for that decision (maintainer to complete):
| standing Critical | attack surface | attacker-dependency | blast radius |
|---|---|---|---|
| (each standing Critical) | … | … | … |
Advisory only — it does not block this review.
— qwen3.8-max via Qwen Code /review (v0.22.2)
| runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'') && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || fromJSON(''["ubuntu-latest"]'') }}' | ||
| timeout-minutes: 30 |
There was a problem hiding this comment.
[Critical] R11-1: still stands from rounds 1-2 (re-asserted in round 9, re-reported as the class finding in rounds 11-13). Routing the release validation jobs onto the shared ecs-qwen pool puts them on members where concurrent/prior same-uid jobs — potentially running contributor code (ci.yml routes same-repo PR lanes onto the same pool) — stay alive through and after the pre-checkout isolation.
Residual same-uid state outside the wiped surfaces (live processes, persistent _work/_actions bundles, runner-owned binaries, the shared Docker daemon) is not closable entrance by entrance. The wipe added by this PR closes only job-owned filesystem state; the surfaces above remain reachable from a release validation job running with the OPENAI_* secrets, and its verdict is what publish trusts. The anchored file is unchanged since this blocker was filed, and this round's only new commit re-arms the size ratchet.
The PR's own Risk & Scope documents this residual risk and names the merge gate: dedicated/drained release-validation members, or per-registration uid isolation plus a private per-job Docker daemon, or explicit maintainer risk acceptance. Per the maintainer hold recorded on this thread, no further per-entrance fix lands until that decision is made.
— qwen3.8-max via Qwen Code /review (v0.22.2)
There was a problem hiding this comment.
Not implemented this round — the thread is left open because the call is a maintainer decision, not one this loop can make.
What this round did change: the sibling Critical R14-1 (one trailing slash defeats the RUNNER_WORKSPACE symlink refusal) is now fixed and witnessed — the raw value is stripped before the -L test, with a behavioral test that turns red when the strip-before--L ordering is removed (mutation-probed). See the round summary for the probe record.
What remains here: as this finding and the PR's Risk & Scope document, the residual same-uid surfaces on the shared pool (live processes, persistent _work/_actions bundles, runner-owned binaries, the shared Docker daemon) are not closable entrance by entrance by this PR. The merge gate those sections name — dedicated/drained release-validation members, per-registration uid isolation plus a private per-job Docker daemon, or explicit maintainer risk acceptance — is a maintainer decision, not a code change. The reviewer's residual-risk assessment this round likewise recommends land-with-residual-risk (merge carrying the residual risk, with the inventory table completed) rather than another review round. The recorded maintainer hold therefore stands, and this thread stays unresolved until that decision is made.
中文说明
本轮未实现该修复——线程保持打开,因为该决定属于维护者决策,不是本循环可以自行作出的。
本轮实际变更的内容:姊妹 Critical R14-1(一个末尾斜杠即可绕过 RUNNER_WORKSPACE 符号链接拒绝检查)现已修复并有测试见证——原始值在 -L 测试之前被去除末尾斜杠,行为测试在移除"先于 -L 的去斜杠"顺序时会变红(已做变异探测)。探测记录见本轮总结。
此处仍然遗留的内容:正如该发现与 PR 的"风险与范围"所述,共享池上残留的同 uid 攻击面(存活进程、持久化的 _work/_actions 包、运行器属主二进制、共享 Docker 守护进程)无法由本 PR 逐个入口关闭。这些章节所列的合并门槛——专用/排空的发布验证成员、按注册隔离 uid 并配备每任务私有 Docker 守护进程、或维护者明确接受风险——是维护者决策,而非代码变更。评审者本轮的残余风险评估同样建议 land-with-residual-risk(带着残余风险合并,并补全清单表格),而非再进行一轮评审。因此已登记的维护者保留意见继续有效,本线程在作出该决定之前保持未解决状态。
There was a problem hiding this comment.
Position unchanged — acknowledging this repost. R11-1 remains the fleet scheduling/isolation property (same-uid co-residency on the shared ecs-qwen pool), not a defect in this PR's job-scoped file-state isolation, which the finding itself re-verifies as sound for the surfaces it covers.
Status update since the last reply: both sibling Criticals are now closed at this head — R14-1 (trailing-slash bypass) at 88eea3f and R15-1 (symlinked intermediate component) at c6bea8a. Verified at c6bea8a: scripts/tests/release-workflow.test.js passes 28/28 when run as a non-root user, and both behavioral pins turn red when their respective fix is mutation-reverted.
The pending maintainer decision is unchanged, as recorded in the prior author replies on this thread: (a) dedicated/drained release-validation members or per-registration uid isolation (plus a private per-job Docker daemon for integration_docker), vs (b) explicit risk acceptance. Per that hold, no further per-entrance code change lands until the ruling, and this thread stays open by design until then.
|
🤖 Addressed the latest review feedback (round 3/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 3/100 轮)。改动内容与我反驳保留之处如下: Autofix review round summary — PR #10036Feedback points and decisions[rc:3883639370] R14-1 (Critical) — trailing slash defeats the
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
4 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- dead pool machinery in the hosted-only publish job (restore-step alias + conditional cache expression) — already discussed, author ruled 'leave it' (comment 3879855513; recorded as D14-2 in round 14)
- forked wipe-guard chain with no cross-file reconciliation pin / new RWS symlink refusal not backported to sibling copies — already reported in the round-9 deferred list (review 5045129895) and the round-14 deferred list (review 5054633750)
- PR description still narrates the replaced git-config scrub and a regression test that no longer exists — already reported in the round-13 deferred list (review 5051903325)
- untested fail-closed wipe arms (heal non-symlink [ ! -d ] leg, refusal arms, realpath-unavailable branches) — declined as R5-7 (comment 3866436468) and tracked in follow-up issue 10176 as R3-4 (comment 3863888365)
Not reviewed: the executable-script lint — Dockerfile: hadolint is not installed.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 15, not a blocker) — recorded, not requested in this round:
.github/workflows/.size-baseline:49 — [review] size baseline records 53022 against an actual 53088 bytes (66 bytes of unreviewed ratchet headroom)
Convergence: round 15 posted 1 inline comment(s), 1 of them reported for the first time; the previous round posted 2 (1 new). Findings keep coming back to the same files: .github/workflows/release.yml (findings in rounds 11, 14; 1 more now). The rate of new findings is not falling. 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. Batching the remaining fixes and verifying them before the next push keeps the loop from re-deriving the same set; this PR's reviews already resolve to a critical posting floor. (Observation only — nothing was withheld from this review because of this observation.)
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
Residual risk: this loop is persistently critical — Criticals stood in the previous round's work-list and stand again this round (2 Critical(s)), the rate of first-time findings is not falling (this round 1, previous 1), and the standing Critical backlog is not shrinking. The severity floor will not converge it. Recommendation: land-with-residual-risk — the exit is a maintainer risk-acceptance decision (merge, carrying the residual risk), not another review round. Residual-risk inventory for that decision (maintainer to complete):
| standing Critical | attack surface | attacker-dependency | blast radius |
|---|---|---|---|
| (each standing Critical) | … | … | … |
Advisory only — it does not block this review.
[Critical] R11-1 (.github/workflows/release.yml:47) — still stands from rounds 1-2 (re-asserted in round 9, re-reported as the class finding in rounds 11-14): routing the release validation jobs onto the shared ecs-qwen pool puts them on members where concurrent/prior same-uid jobs — potentially running contributor code (ci.yml routes same-repo PR lanes onto the same pool) — stay alive through and after the pre-checkout isolation; co-resident state outside the wiped surfaces (live processes, _work/actions bundles, runner-owned binaries, the shared docker daemon, and the unexported QWEN_HOME) remains reachable by the validation jobs' OPENAI* secrets and limited GITHUB_TOKEN. Verified unchanged at this head: no process-inspection logic in the wipe (the reap harm vector was removed by 3c0ca80), QWEN_HOME still unexported. This round's confirmed build-cache sibling folds into this class per the unbounded-surface rule: the digest pin closed the daemon's image-fetch surface, but integration_docker's cache-enabled docker build still consumes the daemon's cross-job layer cache and no pool workflow prunes it (planting mechanism unconfirmed — pool daemon builder topology unknown, hence low confidence on the sibling itself). Carried in the body rather than re-anchored inline: its routing-line anchor is byte-identical across four jobs and has drifted a line between rounds, and the standing thread (comment 3883639358) already carries the claim under the recorded maintainer hold. The exit remains the maintainer decision named in the PR's merge gate: dedicated/drained release-validation members, or per-registration uid isolation plus a per-job private docker daemon, or explicit maintainer acceptance of the residual shared-pool risk.
— qwen3.8-max via Qwen Code /review (v0.22.2)
|
🤖 Addressed the latest review feedback (round 4/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 4/100 轮)。改动内容与我反驳保留之处如下: Round summary — PR #10036 (address-review)Feedback points and dispositions1.
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
5 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- stale .size-baseline entry for release.yml (records 53022, actual at this head is 54395) — already reported in the rounds 6-15 lineage, most recently the round-15 deferred list (review 5055758637)
- forked wipe-guard chain with no cross-file reconciliation pin / new RWS symlink refusal not backported to sibling copies — already reported in the round-9 deferred list (review 5045129895) and the round-14 deferred list (review 5054633750)
- dead restore-step alias and conditional cache expression on the hosted-only publish job — already discussed as D14-2 (comment 3879855513); author ruled 'leave it'
- heal non-symlink leg [ ! -d "$WS" ] has no scenario — declined by the author as R5-7 (comment 3866436468), tracked in follow-up issue 10176
- bare find core deletion depends on the runner's implicit -e with no explicit fail-closed annotation — already reported in the round-9 deferred list (review 5045129895)
Not reviewed: the executable-script lint — Dockerfile: hadolint is not installed.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 16, not a blocker) — recorded, not requested in this round:
.github/workflows/release.yml:104 — [probe] D16-4 refusal echoes print unsanitized attacker-chosen path bytes (:: workflow-command injection).github/workflows/release.yml:202 — [probe] D16-5 user-wide git attributes/ignore files bypass the GIT_CONFIG_GLOBAL isolationscripts/tests/release-workflow.test.js:437 — [probe] D16-1 git-isolation oracle accepts any nonzero exit — missing-file mutant ships greenscripts/tests/release-workflow.test.js:345 — [probe] D16-2 chown/chmod ownership ladder has zero behavioral coveragescripts/tests/release-workflow.test.js:27 — [review] D16-3 GNU-realpath probe and exec harness duplicated from serve-ab-workflow.test.js, already divergent
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
[Critical] R11-1: still stands from rounds 1-2 (re-asserted in round 9, re-reported as the class finding in rounds 11-15). Routing the release validation jobs onto the shared ecs-qwen pool puts them on members where concurrent/prior same-uid jobs — potentially running contributor code (ci.yml routes same-repo PR lanes onto the same pool) — stay alive through and after the pre-checkout isolation; co-resident state outside the wiped surfaces (live processes, _work/actions bundles, runner-owned binaries, the shared docker daemon, and the unexported QWEN_HOME) remains reachable by the validation jobs' OPENAI* secrets and limited GITHUB_TOKEN. Verified unchanged at this head: the wipe still scopes to job-owned filesystem/HOME/config state only, no process-level isolation exists (the reap harm vector was removed by 3c0ca80), QWEN_HOME is still not among the GITHUB_ENV isolation exports, and the PR's own Risk & Scope documents this residual risk and names the merge gate. This round's newly confirmed low-confidence sibling folds into this class per the unbounded-surface rule: the sandbox image integration_docker executes is still consumed by mutable tag on the shared daemon (a co-resident job able to run docker tag between Build Sandbox and the vitest steps could substitute it; the premise — two jobs concurrently sharing one daemon — is unconfirmed from the repo, pool daemon/host topology unrecorded; hence low confidence on the sibling itself, reported in the terminal only). Carried in the body rather than re-anchored inline: its routing-line anchor is byte-identical across four jobs and has drifted a line between rounds, and the standing thread (comment 3883639358) already carries the claim under the recorded maintainer hold. The exit remains the maintainer decision named in the PR's merge gate: dedicated/drained release-validation members, or per-registration uid isolation plus a per-job private docker daemon, or explicit maintainer acceptance of the residual shared-pool risk.
— qwen3.8-max via Qwen Code /review (v0.22.3)
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix round: no code changesNo code changes were made this round. Every item in the round-16 review either keeps its earlier-round disposition, was explicitly recorded as not requested this round, or is blocked on a maintainer decision this loop cannot make. No inline comments, issue-level comments, failed checks, or still-red checks arrived this round. Critical R11-1 (shared-pool residual risk) — remains open for the maintainer decisionThe finding stands as reported at this head; its factual claims were re-verified against the current code before this classification:
Classification: needs a maintainer's decision — not declined, not deferred, not implemented. The exit this finding names is exactly the merge gate the PR's own Risk & Scope section documents, and none of its three options is a code change this bot can make and verify:
The standing thread (discussion_r3883639358) already carries the claim under the recorded maintainer hold, with the previous round's reply stating the same position; it stays unresolved until the maintainer answers. No partial per-entrance hardening was added this round: under the recorded hold ("no further per-entrance fix lands until that decision is made") and with the finding's exit being the decision itself, adding fragmentary guards would grow the diff without closing the class finding. Five Suggestion-level findings — earlier dispositions stand, not re-openedThe round-16 review confirms these are already reported and does not repeat them as new work. Their dispositions are unchanged:
Five round-16 probe findings — deferred by the review itself, no work requestedD16-1 through D16-5 (unsanitized refusal echo; user-wide git attributes/ignore bypass of ChecksNo failed or still-red checks this round; the PR's required checks are green at this head. 中文说明Autofix 轮次:无代码变更本轮未做任何代码变更。第 16 轮评审中的每一个条目,要么沿用此前轮次的既有处置,要么被明确记录为"本轮不要求处理",要么被一个本循环无权作出的维护者决策所阻塞。本轮没有收到任何行内评论、issue 级评论、失败检查或持续失败的检查。 Critical R11-1(共享池残余风险)——保持打开,等待维护者决策该发现在当前 head 上依然成立;在作出此分类之前,其事实性论断已对照当前代码重新核实:
分类:需要维护者决策——既非拒绝,也非延期,更未实现。 该发现所指出的出口,正是本 PR 自身"风险与范围"(Risk & Scope)章节所记录的合并门槛,而其三个选项没有一个是本机器人能够实施并验证的代码变更:
常设线程(discussion_r3883639358)已在已记录的维护者搁置(maintainer hold)之下承载了该论断,且上一轮的回复已表明同样立场;该线程将保持未解决状态,直到维护者给出答复。本轮没有添加任何局部性的逐入口加固:在已记录的搁置("在作出该决策之前,不再落地任何逐入口修复")之下,且该发现的出口本身就是这一决策,添加碎片化的守卫只会增大 diff,却无法关闭该类发现。 五个 Suggestion 级发现——沿用既有处置,不重新打开第 16 轮评审确认这些发现已被报告过,且未将其作为新工作重复提出。其处置保持不变:
五个第 16 轮探测发现——评审自身已延期,本轮不要求处理D16-1 至 D16-5(拒绝回显未净化;用户级 git attributes/ignore 绕过 检查本轮没有失败或持续失败的检查;在当前 head 上,该 PR 的必需检查全部为绿色。 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix round: no actionThis round's feedback contains no new findings. All five inline items are Triage of the five items
Evidence gathered this round (re-verification, no code change)
Because both "Fixed" claims reproduce at the current head and the three 中文说明Autofix 本轮:无需操作本轮反馈中没有新的发现项。五条行内评论均为作者回复,其中声明的内容已在当前 五个条目的分类处理
本轮收集的证据(重新核实,未改动代码)
由于两个“已修复”声明在当前 head 上均可复现,且三条 R11-1 重发均带有明确的 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 128 passed · 0 failed · 128 total Flakiness gate: ✅ 1 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:128 通过 · 0 失败 · 128 总计 抖动门:✅ 1 changed test file(s) x 5 identical rounds, no divergence Verification reportPR #10036 Deep Verification (round 17) —
|
| # | Round-16 finding | Round-16 sev | Status at c6bea8a6f47 (round 17) |
|---|---|---|---|
| B1 | Suite red at head: happy-path still asserted the removed purge | Blocking | Fix still holds. 28/28 green ×5 at the new head (01-suite-head-green-x5.png); the behavioral wipe test executes (not skipped). Suite grew 26→28 (2 CUA tests arrived via the main merge). |
| B2 | Reap witness can never pass | Blocking | Still superseded by mechanism removal. Kill-free census re-run: 0 process verbs in the payload, 0 on any code line of release.yml. |
| F1 | Tool-cache containment refuses the pool's real sibling layout | fixed r14 | Fix still holds. E5 cells both arms: sibling _work/_tool/node/marker.txt survives the wipe. |
| F2 | Changed test file not prettier-clean | fixed r14 | Superseded by deletion. 3dd2329c reverted the reflow; teamHelpers.test.ts is byte-identical to the base tip and no longer part of the PR. Changed files prettier-clean + liveness control red. |
| F4 | Risk & Scope understates jobs carrying secrets onto the pool | Low | Fixed. The body now reads "quality, integration_none, and integration_docker also receive OPENAI_* secrets" — and the claim is factually correct: exactly those three jobs carry OPENAI_API_KEY (lines 325/401/466), all three ECS-routed. |
| F5 | chmod guard pinned by nothing | superseded r14 | Still superseded. Geometry-first re-proven: E1/E3 refusals fire before any mutation — decoy content and mode 0400 intact, zero GITHUB_ENV exports written. |
| F6 | Docker preflight SC2250 | fixed r15 | Fix still holds. shellcheck warning+ zero findings on the preflight (liveness control catches planted SC2154). Under the optional --enable=all style lens: 1 note, unchanged. |
| F7 | PR body describes the removed scrub/lock mechanism | Low | Stands (Finding F7 below). All four stale claims re-measured present; the suite now has 28 tests, not 23. |
| F8 | Tool-cache purge evicts the pool-wide cache | fixed r15 | Fix still holds. Purge still absent (census); survival proven behaviorally (E5) and the kill-free/byte-mirror pins remain armed (M-KILL, M-BYTE red). |
| F9 | Size baseline drifted (recorded 54000 vs actual 57598 → 58255 vs 53022) | Low (r15) | Fixed by 1ae35889, new residual nit. The re-arm recorded 53022, and at that commit release.yml was exactly 53022 bytes (git cat-file -s — the "true floor" claim verified). The two guard commits after it grew the file to 54395, so the entry is now under-recorded by 1373 — inside the +4096 allowance (gate green, 9/9 with 3 threshold controls), direction reversed from round 16. See nit below. |
Central claim + A/B
Central claim of this round: the two delta guard hunks are load-bearing — every symlink re-rooting shape (leaf with and without trailing slashes, intermediate component above the runner workspace, intermediate component below it) is refused before any chown/chmod/wipe, and the guards do not false-positive on the pool's real geometry. Secondary claims: the size re-arm is exact and the gate stays armed; every carried mechanism survives the delta.
This verify container is itself a job on the target pool, so the pool's real geometry (/__w all-real components, GNU realpath 9.1, uid 1000) is directly measurable; harnesses drove YAML-extracted payloads (jobs.prepare.steps[0].run, the &restore_release_workspace anchor aliased into the other four jobs) under bash -e -o pipefail against mkdtemp fixtures; live pool paths were never written.
Escape A/B — the delta closes four realized holes (harness 02, 38 assertions). Witness: 03-payload-ab-r16-escapes-vs-head-refusals.png
| shape | round-16 payload (control) | head payload |
|---|---|---|
| E1 symlinked RWS leaf + one trailing slash, workspace through the link | escape: exit 0, decoy wiped through the link | refused at -L after the new strip; decoy intact |
| E2 same, workspace already resolved (WS_LEX cannot see it) | escape; head-minus-strip also refuses (RWS_LEX catches — layers substitutable); head-minus-strip-AND-RWS_LEX escapes | refused; decoy intact |
E3 symlinked _work (intermediate component above RWS) |
escape: exit 0, decoy tree wiped | refused "resolves through a symlinked component"; decoy + mode 0400 intact; zero exports |
| E4 symlinked intermediate BELOW RWS (workspace side) | escape: the wrong sibling directory wiped | refused on the workspace-side comparison; decoy intact |
| E5 happy path + pool-sibling geometry | exit 0, workspace emptied, _tool/node survives |
identical, plus all 7 isolation exports in $GITHUB_ENV |
| E6 triple trailing slash | escape | refused |
| E7 live pool truth | — | every component of /__w/qwen-code/qwen-code and /__w/_temp real; lexical == canonical (no false positive) |
[ -L "/path/link/" ] returns false on Linux (the trailing slash forces resolution), which is why E1/E6 pierced the round-16 leaf check; realpath -m then canonicalized THROUGH the link and the containment allow-list re-rooted at the attacker-chosen target. The E2 ladder additionally shows the new layers are defense-in-depth: strip and RWS_LEX are substitutable for that shape, and the escape reopens only when both are absent (or when WS_LEX is also absent — the M-SET row).
Mutation matrix — 7 cells on the green baseline (harness 01, 27 assertions). Witness: 02-mutation-matrix-delta-guards.png
Mutants applied lockstep (release.yml payload + the canonicalWipe byte mirror together) where the mirror would mask, release.yml-only for M-BYTE; sha256-verified restores after every cell:
| cell | mutant | suite result | kill mechanism |
|---|---|---|---|
| C0 | none | 28 passed | green baseline |
| M-BYTE | 1-char comment change, release.yml only | 1 failed — exactly cleans every shared ECS workspace before checkout |
byte mirror alive (positive control) |
| M-STRIP | drop the new pre--L trailing-slash strip |
1 failed — exactly the behavioral test | message pin: the sibling RWS_LEX layer still refuses, but with the component message — redundant defence, sharpens the diagnostic |
| M-LEX-RWS | drop the RWS lexical-vs-canonical block | 1 failed — exactly the behavioral test | message pin: WS_LEX catches the suite's fixture through the workspace path — same layered shape |
| M-LEX-WS | drop the WS lexical-vs-canonical block | 1 failed — exactly the behavioral test | behavioral: expect(result.status).not.toBe(0) fails — exit 0, the escape is real (no sibling covers below-RWS links); unconditionally load-bearing |
| M-KILL | append ps -e > /dev/null |
1 failed — exactly the kill-free test | continuity pin alive at this head |
| M-SET | all three delta hunks reverted together (= round-16 payload) | 1 failed — exactly the behavioral test | behavioral at the trailing-slash cell (:702): the set as a whole is load-bearing |
Suite A/B continuity: round 16 proved the round-15 closure red ×5; at this head the suite is deterministically green ×5 (28/28, 0 divergence — 01-suite-head-green-x5.png), and the behavioral test runs rather than skips (GNU realpath present, uid ≠ 0, Linux).
Corrections
- To the PR body's mechanism description, Reviewer Test Plan step 4, and "Tested on" line (carried from rounds 14–16, re-measured at this head, still inaccurate): "the global Git-config scrub now fails closed if a dangerous exec key cannot be removed or verified", "Confirm a planted global Git exec key is removed normally and a lock that prevents removal fails before checkout", and "23 passed, including the
.gitconfig.lockfail-closed regression" all describe the scrub mechanism this PR's own isolation commits replaced. Measured again: release.yml contains zero--unset/lock/scrub code lines (census); the suite has 28 tests and no lock test;.gitconfig.lockexists only as a stale-leftover fixture in the behavioral test. The replacement safety property — planted pool state is invisible to every step after the restore step (redirected fresh state dirs), and the wipe refuses any geometry it has not validated — holds and is proven. Step 4 as written cannot be performed. (Correction to the description — no code change implied; the code is verified sound.) - To the body's "wipe the persisted workspace and HOME state" (carried): the workspace is wiped; the HOME state is redirected to fresh per-job state directories (
GIT_CONFIG_GLOBAL/NPM_CONFIG_USERCONFIG/DOCKER_CONFIG/GH_CONFIG_DIR), not wiped — planted files remain on disk but inert.
The body's F4-relevant sentence ("quality, integration_none, and integration_docker also receive OPENAI_* secrets") is new since round 16 and was verified true against the YAML — no correction needed there.
Findings
F7. Low (carried, re-measured) — the PR body's verification plan and counts describe a mechanism this PR removed
All four stale claims measured present at this head: the fail-closed-scrub sentence, step 4's lock scenario (structurally unperformable — no scrub/lock code exists), "23 passed" (suite has 28), and the .gitconfig.lock regression citation (no such test). One body edit closes it; see Correction 1 for the replacement wording. No code change implied.
Nit (new, informational) — size baseline under-recorded by 1373 bytes at the merge head
1ae35889 re-armed the ratchet at 53022, which was byte-exact the file's size at that commit (verified with git cat-file -s); the two guard commits that followed (88eea3f4, c6bea8a6) grew release.yml to 54395 without touching the entry. Consequences measured against the real gate script: green today (1373 < the 4096 allowance — K0 ran the committed baseline, K1–K3 the threshold controls), the ratchet simply sits 1373 bytes tighter than the true floor, and a future +2724-byte edit trips it. Direction reversed from round 16's over-recording and harmless either way inside the band; lowering the entry to 54395 release.yml would re-arm at the true floor. Completeness only.
Note — layered guards, classified (not a finding)
M-STRIP and M-LEX-RWS were killed, but by the pinned refusal messages, not by realized escapes: their sibling layers still refuse the same shapes (the failure logs show the assertion mismatch is the message text), so without those message pins the two mutants would have survived. The E2 ladder shows strip/RWS_LEX are substitutable for the resolved-workspace shape. This is redundant defence inside a set whose combination row (M-SET) is behaviorally load-bearing — the correct shape for a wipe guard, reported so the author knows each single-hunk revert is caught by pin, not by escape.
Not covered
- Live runner selection and a real release dispatch — label matching happens at dispatch time, outside this sandbox; the body says a live dispatch was intentionally not triggered.
- Windows-lane execution of the behavioral suite — the
skipIfgate is unchanged by the delta; no Windows host here (carried). - The docker daemon on a pool member — this container has no docker socket; the preflight's fail-closed form is bash-valid, shellcheck-clean at warning+, and pinned by the suite's regex test, but a live
docker infowas not exercised (shape reproduced, not the runner-environment trigger; carried). - Per-commit attribution — depth-2 checkout:
git rev-list HEAD^1..HEAD^2returns 1 against the snapshot's 46 commits (shallow-boundary artifact). Only the aggregateHEAD^1..HEADdiff and the round-16→head delta (via the leftover round-16 object) were exercisable. - The round-16 isolation wire oracles (git/npm/gh token capture) — carried on a proven-identical input closure: the payload's export section (
release_state=to end) is byte-identical between the round-16 and round-17 payloads, and the head happy-path cell re-proved reachability at this head (all 7 exports land in$GITHUB_ENV). The wire probes themselves were not re-executed this round. - The round-16 48-cell routing truth table — carried on a proven-identical input closure:
runs-onof all six jobs byte-identical between the two heads; the two routing tests pass at head. - yamllint — no
pip3in this container (carried); actionlint parsed the YAML live and theyamlpackage parses it in every harness. - Repo-wide typecheck/build — the effective diff contains no TypeScript at all this round (the teamHelpers reflow was reverted).
- Root-runner skip paths — uid here is 1000; the
RUNNER_UID == 0branches were inspected, not executed (carried). - Harness disclosure — the first E2 run predicted
head-minus-stripwould escape; measurement showed RWS_LEX alone catches that shape (exit 1). The harness expectation was corrected to the measured truth and re-run; the corrected cell is what counts. One shellcheck liveness probe was likewise rebuilt (SC2086 is info-level, below the asserted warning+ bar; replaced with warning-level SC2154) — an instrument fix, not a PR observation.
Methodology
One CI verify container (node:22-bookworm, uid 1000, GNU realpath 9.1, no sudo/docker/zip/pip3, no GitHub token) that is itself a job on the target ecs-qwen pool. Merge-ref checkout (HEAD 5de53ea3fd, HEAD^1 base e5cb60ad48, HEAD^2 PR head c6bea8a6f4); the round-16 head 5222836240c4 was available as a leftover object and served as the control payload source. Scratch worktrees under tmp/pr10036-verify-20260829-045749/ (wt for the mutation matrix, base-tree for the A/A attribution), both removed --force afterwards and the tracked tree verified clean. Harnesses (all in the artifact dir, rerunnable): 00 suite stability (5 runs + verbose executed-not-skipped check), 01 mutation matrix (7 cells, lockstep edits with anchor-occurrence validation — the payload exists once in release.yml as a YAML anchor aliased four times, so single-site edits mutate all five resolved copies; sha256-verified restores), 02 payload escape A/B (four escape shapes × r16/head arms plus two head-minus-guard payloads from the matrix, happy path, live-pool component walk), 03 carried re-measurements (F4/F7/F9 censuses, routing + isolation closure byte comparisons, anchor equivalence, kill-free census, teamHelpers drop-out), 04 size gate (real check-workflow-size.sh + three threshold controls on mutated baselines), 05 lint/digest gates (actionlint 1.7.12 and shellcheck 0.11.0 installed via the repo's scripts/lint.js --setup with sha256-verified archives; extracted run blocks given a bash shebang to suppress the SC2148 harness artifact; prettier with the repo config for real checks and --ignore-path /dev/null for the liveness control because tmp/ is prettier-ignored; digest compared against the live registry token flow), 06 assertion collector (assertions.json from the A| lines of logs 00–05). npm run test:scripts: 1800 passed / 15 failed — all 15 EACCES in check-tui-dep-direction.test.js (root-owned read-only .qwen checkout metadata in the main tree) with A/A proof (67/67 green on the base worktree whose .qwen is uid-1000-writable; file unchanged by the PR), plus install-script.test.js failing collection by design (container ships unzip but no zip). Evidence captures: scripts/verify-capture.mjs rendered each harness run as it printed (5 images below); raw logs per harness in logs/.
Evidence images
01-suite-head-green-x5.png— suite at head: 28/28 ×5, zero divergence02-mutation-matrix-delta-guards.png— 7-cell mutation matrix as printed03-payload-ab-r16-escapes-vs-head-refusals.png— escape A/B: r16 wipes through all four shapes, head refuses all04-carried-findings-remeasured.png— carried findings re-measured (F4 fixed in body, F7 stands, F9 direction flipped)05-gates-size-ratchet-lint-digest.png— size ratchet + threshold controls, lint gates live, digest == live registry
Flakiness gate log
rounds=5 files=1 skipped=0
file scripts/tests/release-workflow.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/release-workflow.test.js
per-file results (P=pass F=fail I=infra-exit, one letter per run):
scripts/tests/release-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/release-workflow.test.js: P (exit 0)
round 2 · scripts/tests/release-workflow.test.js: P (exit 0)
round 3 · scripts/tests/release-workflow.test.js: P (exit 0)
round 4 · scripts/tests/release-workflow.test.js: P (exit 0)
round 5 · scripts/tests/release-workflow.test.js: P (exit 0)
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
|
Triage re-run completed without a new review. The bot already has a review of its own on 机器人在 The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix round: no actionThis round's feedback contains nothing actionable, so no code change was made What was reviewed
Diff growthNet growth this window: source 19 / test 195 lines against budgets of 400/400, VerificationNo verification commands were run because no code was changed; the working 中文说明Autofix 本轮:无需操作本轮反馈中没有任何需要处理的内容,因此未做任何代码改动,也未创建提交。 审阅内容
差异增长本窗口净增长:源码 19 行 / 测试 195 行,预算为 400/400,且此前没有超出预算的轮次——无需合并或删减代码。 验证由于未改动任何代码,本轮未运行验证命令;工作树保持不变,仍为 c6bea8a。 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
Independent verification of #10036 — real-environment reportI built a real verification environment for this PR rather than reading it: three GitHub Actions runs on a fork, plus a local harness that executes the byte-identical Verdict: recommend merge. I found no blocker. All five copies of the wipe step (one anchor, four aliases) are byte-identical, the guard chain refuses every hostile geometry I could construct before touching anything, the hosted emergency fallback is effectively the pre-PR pipeline, and the new tests kill every mutant I aimed at them. Three items below are worth a decision before the first real release run on the pool. What ran, and where
1 — Real GitHub Actions runs1.1 The YAML anchor parses. This PR introduces the only 1.2 The hosted fallback leg resolves. The 1.3 The 1.4 The wipe works end to end on a real runner. With the gate removed for the probe, against a workspace seeded with 2 — Guard-chain geometry matrix (local, byte-identical step)Fixtures are built as root (so the root-owned leftovers are genuinely root-owned) and the step then runs as an unprivileged uid with no sudo rights — the shape a pool member has after a containerized co-resident job. 17 of 18 behave exactly as designed. Every hostile geometry refuses before any 3 — Do the new tests actually catch regressions?
4 — Static and supply-chain checks
FindingsF1 — The PR description no longer matches the diff (Important, docs)The body describes a mechanism that was replaced during the PR's 46 commits:
The code is fine; the description is what a future reader will be misled by. F2 — A root-owned leftover directory wedges the pool lane without passwordless sudo (Important, operational)Reproduced, and A/B'd: Both Worth either guaranteeing F3 —
|
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 165 passed · 0 failed · 165 total Flakiness gate: ✅ 1 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:165 通过 · 0 失败 · 165 总计 抖动门:✅ 1 changed test file(s) x 5 identical rounds, no divergence Verification reportPR #10036 Deep Verification (round 18) —
|
| # | Round-17 finding | Round-17 sev | Status at c6bea8a6f47d (round 18) |
|---|---|---|---|
| B1 | Suite red at head: happy-path asserted the removed purge | Blocking | Fix still holds. 28/28 green ×10 this round (5 logged rounds + 5 capture rounds, zero divergence; behavioral test executed, not skipped — 01-suite-head-green-x6.png). |
| B2 | Reap witness can never pass | Blocking | Still superseded by mechanism removal. Kill-free census re-run: 0 process verbs in the payload, 0 kill/pkill/pgrep code lines in release.yml. |
| F1 | Tool-cache containment refuses the pool's real sibling layout | fixed r14 | Fix still holds. E5 re-run on both arms: sibling _work/_tool/node/marker.txt survives the wipe. |
| F2 | Changed test file not prettier-clean | fixed r14 | Superseded by deletion. Effective diff asserted to be exactly the 4 known files — teamHelpers.test.ts absent. |
| F4 | Risk & Scope understates jobs carrying secrets onto the pool | Low | Fixed. Carriers are exactly quality, integration_none, integration_docker (all ECS-routed); the body sentence names exactly those three. |
| F5 | chmod guard pinned by nothing | superseded r14 | Still superseded. E1/E3 refusal arms re-prove refusals fire BEFORE any mutation: decoy content and mode 0400 intact, zero GITHUB_ENV exports written. |
| F6 | Docker preflight SC2250 | fixed r15 | Fix still holds. shellcheck warning+ zero findings on the preflight; under --enable=all: exactly 1 note (SC2250), unchanged. |
| F7 | PR body describes the removed scrub/lock mechanism | Low | Stands (Finding F7 below). All four stale claims re-measured present; the suite has 28 tests, not 23. |
| F8 | Tool-cache purge evicts the pool-wide cache | fixed r15 | Fix still holds. Purge still absent (census); survival re-proven behaviorally (E5). |
| F9 | Size baseline drifted | Low | Fixed by 1ae35889, residual nit stands. Entry 53022, file 54395, under-recorded by exactly 1373 — inside the +4096 allowance (K0 green; K1–K3 threshold controls re-run). |
| Nit | Baseline under-recorded by 1373 bytes after the re-arm | Informational | Stands — same measurement as F9's residual above; no code consequence inside the band. |
Central claim + A/B
Central claim: the delta guard hunks (pre--L trailing-slash strip; RWS lexical-vs-canonical comparison; WS lexical-vs-canonical comparison) are load-bearing — every symlink re-rooting shape is refused before any chown/chmod/wipe — and they do not false-positive on the pool's real geometry. Secondary claims: the size re-arm keeps the ratchet armed and green; every carried mechanism survives.
Control-arm identity. The control is M-SET(head): the head payload minus exactly the three delta hunks. The leftover round-16 head object (5222836240c4, still present in this member's object store) yields a payload byte-identical to M-SET(head) modulo comment lines — the delta commits added two comment blocks together with the guard code (asserted; see Corrections). Payload sizes: head 8368 B, M-SET 7663 B, r16 7187 B.
Escape A/B (harness h02, 43 assertions). Witness: 03-escape-ab-control-escapes-head-refuses.png
| shape | control (head minus delta hunks) | head |
|---|---|---|
| E1 symlinked RWS leaf + one trailing slash | escape: exit 0, decoy wiped through the link | refused at the leaf -L check after the new strip; decoy intact |
| E2 same, workspace already resolved | escape; head-minus-strip also refuses (RWS_LEX catches — layers substitutable); head refuses | refused; decoy intact |
E3 symlinked _work (intermediate above RWS) |
escape: exit 0, decoy tree wiped | refused "resolves through a symlinked component"; decoy + mode 0400 intact; zero exports |
| E4 symlinked intermediate below RWS | escape: the wrong sibling directory wiped | refused on the workspace-side comparison; decoy intact |
| E5 happy path + pool-sibling geometry | exit 0, workspace emptied, _tool/node survives, 7 exports |
identical (export sections byte-identical since r16) |
| E6 triple trailing slash | escape | refused |
| E7 live pool truth | — | every component of /__w/qwen-code/qwen-code and /__w/_temp real; lexical == canonical (no false positive) |
[ -L "/path/link/" ] returns false on Linux (the trailing slash forces resolution), which is why E1/E6 pierce the control's leaf check; realpath -m then canonicalizes THROUGH the link and the containment allow-list re-rooted at the attacker-chosen target.
Mutation matrix (harness h01, 37 assertions). Witness: 02-mutation-matrix-delta-guards.png
Lockstep edits (release.yml payload + the canonicalWipe byte mirror together; release.yml-only for M-BYTE); sha256-verified restores after every cell; tracked tree clean at the end:
| cell | mutant | suite result | kill mechanism |
|---|---|---|---|
| C0 | none | 28 passed | green baseline |
| M-BYTE | 1-char comment change, release.yml only | 1 failed — exactly cleans every shared ECS workspace before checkout |
byte mirror alive (positive control) |
| M-STRIP | drop the pre--L trailing-slash strip |
1 failed — exactly the behavioral test | message pin: sibling RWS_LEX still refuses, with the component message — redundant defence |
| M-LEX-RWS | drop the RWS lexical-vs-canonical block | 1 failed — exactly the behavioral test | message pin: WS_LEX catches the fixture through the workspace path — same layered shape |
| M-LEX-WS | drop the WS lexical-vs-canonical block | 1 failed — exactly the behavioral test | behavioral: expected +0 not to be +0 at release-workflow.test.js:846 (below-RWS fixture) — escape is real; unconditionally load-bearing |
| M-KILL | append ps -e > /dev/null |
1 failed — exactly the kill-free test | continuity pin alive at this head |
| M-SET | all three delta hunks reverted together | 1 failed — exactly the behavioral test | behavioral at the trailing-slash fixture (:710): the set as a whole is load-bearing |
Suite stability (harness h00). Witness: 01-suite-head-green-x6.png
28/28 ×10 identical rounds this round (5 logged + 5 under capture), zero divergence; the behavioral test carries a ✓ marker (executed, not skipped: GNU realpath 9.1 present, uid 1000, Linux).
Full scripts gate (harness h06, 11 assertions)
npm run test:scripts: 1800 passed / 15 failed; 2 failed files / 69 passed files — the same totals as round 17, and both failing files adjudicated environmental with both arms executed this round: check-tui-dep-direction.test.js is 67/67 green on the base worktree (whose .qwen is uid-1000-writable) and red with EACCES on the main tree (root-owned read-only .qwen); install-script.test.js throws at collection by design (zip absent from this container, unzip present). release-workflow.test.js is green inside the full run.
Corrections
- To the round-17 report's control-arm description (not a PR defect): round 17 described M-SET as "= the round-16 payload". Measured stricter this round: the two are byte-identical modulo the two comment blocks the delta commits added alongside the guard code (7187 B vs 7663 B). Behaviorally identical — comments do not change bash — and round 17's verdict is unaffected; the record is corrected so the control's identity is exact.
- To the PR body's mechanism description, Reviewer Test Plan step 4, and "Tested on" line (carried from rounds 14–17, re-measured, still inaccurate): "the global Git-config scrub now fails closed if a dangerous exec key cannot be removed or verified", "Confirm a planted global Git exec key is removed normally and a lock that prevents removal fails before checkout", and "23 passed, including the
.gitconfig.lockfail-closed regression" all describe the scrub mechanism this PR's own isolation commits replaced. Measured again: release.yml contains zero--unset/gitconfig.lock/scrub code lines (census); the suite has 28 tests and no lock test. Step 4 as written cannot be performed. (Correction to the description — no code change implied; the code is verified sound.) - To the body's "wipe the persisted workspace and HOME state" (carried): the workspace is wiped; the HOME state is redirected to fresh per-job state directories (
GIT_CONFIG_GLOBAL/NPM_CONFIG_USERCONFIG/DOCKER_CONFIG/GH_CONFIG_DIR), not wiped.
Findings
F7. Low (carried, re-measured) — the PR body's verification plan and counts describe a mechanism this PR removed
All four stale claims measured present at this head: the fail-closed-scrub sentence, step 4's lock scenario (structurally unperformable — no scrub/lock code exists), "23 passed" (suite has 28), and the .gitconfig.lock regression citation (no such test). One body edit closes it; see Correction 2 for the replacement wording. No code change implied.
Nit (carried, informational) — size baseline under-recorded by 1373 bytes at the merge head
Entry 53022 release.yml vs actual 54395 — re-measured exactly 1373 under, inside the +4096 allowance. Gate green today (K0 ran the committed baseline; K1 re-arm at the true floor green with no warnings; K2 one byte past allowance correctly fails with grew to 54395 bytes; K3 past the slack warns). Lowering the entry to 54395 release.yml would re-arm at the true floor. Completeness only.
Note — layered guards, classified (not a finding)
M-STRIP and M-LEX-RWS were killed by pinned refusal messages, not realized escapes (the failure logs show the assertion mismatch is the message text): their sibling layers still refuse the same shapes. This is redundant defence inside a set whose combination row (M-SET) is behaviorally load-bearing — the correct shape for a wipe guard.
Not covered
- Live runner selection and a real release dispatch — label matching happens at dispatch time, outside this sandbox; the body says a live dispatch was intentionally not triggered.
- Windows-lane execution of the behavioral suite — the
skipIfgate is unchanged; no Windows host here (carried). - The docker daemon on a pool member — this container has no docker socket; the preflight's fail-closed form is bash-valid, shellcheck-clean at warning+, and pinned by the suite's regex test, but a live
docker infowas not exercised (shape reproduced, not the runner-environment trigger; carried). - Per-commit attribution — depth-2 checkout:
git rev-list HEAD^1..HEAD^2returns 1 against the snapshot's 46 commits (shallow-boundary artifact). Only the aggregateHEAD^1..HEADdiff was exercisable (carried). - The round-16 isolation wire oracles (git/npm/gh token capture probes) — carried on the re-proven input closure: the payload's export section is byte-identical between the head payload and the round-16 object payload (asserted this round), and the head happy-path cell re-proved reachability (all 7 exports land in
$GITHUB_ENV). The probes themselves were not re-executed. - yamllint — no
pip3in this container (carried); actionlint parsed the YAML live and theyamlpackage parses it in every harness. - Repo-wide typecheck/build — the effective diff contains no TypeScript.
- Root-runner skip paths — uid here is 1000; the
RUNNER_UID == 0branches were inspected, not executed (carried). - Harness disclosure — six instrument defects found and fixed this round, none of them PR observations: (a) E1/E6 originally reused the fixture across arms, so the control's escape destroyed the decoy the head arm then asserted on — fixed with a fresh fixture per arm and re-run; (b) vitest 3.2.7 failure lines carry ANSI escapes and a file-path prefix my parser first missed; (c) the shellcheck liveness probe first planted an ALL_CAPS variable, which SC2154 exempts as a presumed environment variable — replaced with a lowercase one; (d) actionlint exits 3 outside a git project — the liveness scratch dir now
git inits; (e) prettier has no Dockerfile/.size-baselineparser — both measured and excluded from the check rather than failed; (f) body censuses first missed backtick-wrapped claims and YAML-unescaped''quotes. Two round-17 attribution markers (expected 0 to not be 0) were also corrected to vitest's actual wording (expected +0 not to be +0) with the fixture line captured from live runs.
Methodology
One CI verify container (node:22-bookworm, uid 1000, GNU realpath 9.1, node v22.23.2, no sudo/docker/zip/pip3, outbound network available) that is itself a job on the target ecs-qwen pool. Merge-ref checkout (HEAD 5de53ea3fd, HEAD^1 base e5cb60ad48, HEAD^2 PR head c6bea8a6f4) — byte-identical to round 17's ref. Harnesses (all in the artifact dir, rerunnable; logs per harness in logs/, assertion lines prefixed A|): h00 suite stability (5 logged rounds + 5 capture rounds, verbose executed-not-skipped check); h01 mutation matrix (7 cells, lockstep edits with single-occurrence anchor validation — the payload exists once in release.yml as a YAML anchor aliased four times, so single-site edits mutate all five resolved copies; sha256-verified restores; git status --porcelain clean at the end); h02 escape A/B (four escape shapes × control/head arms plus the E2 head-minus-strip ladder, happy path on both arms, live-pool component walk; control identity proven against the leftover round-16 object modulo comments); h03 carried censuses (F4/F7/F9, routing census + 6-cell truth table evaluated under GitHub left-associative &&/|| semantics, kill-free census, wire closure, effective-diff list); h04 size gate (the committed check-workflow-size.sh against the real workflow tree in hermetic scratch copies + three threshold controls); h05 lint/digest gates (actionlint 1.7.12 and shellcheck 0.11.0 from the repo's scripts/lint.js --setup with sha256-verified archives, repo actionlint flags, shellcheck warning+ on the extracted run blocks, prettier with the repo config, digest compared against the live registry token flow — pinned digest resolves, contains linux/amd64, and still equals the live 22-slim tag digest); h06 full test:scripts adjudication + two-arm A/A for the environmental failures (base worktree under tmp/…/scratch/base-tree, removed --force afterwards, tracked tree verified clean). Evidence captures: scripts/verify-capture.mjs rendered six harness runs as they printed (images below).
Evidence images
01-suite-head-green-x6.png— suite at head: 28/28, zero divergence (rounds 6–10 under capture)02-mutation-matrix-delta-guards.png— 7-cell mutation matrix as printed, kill evidence included03-escape-ab-control-escapes-head-refuses.png— escape A/B: control wipes through all four shapes, head refuses all04-carried-findings-remeasured.png— carried findings re-measured (F4 fixed, F7 stands, F9 nit, routing truth table)05-size-ratchet-gate.png— size ratchet K0–K306-lint-digest-gates.png— lint gates live, digest == live registry tag
Flakiness gate log
rounds=5 files=1 skipped=0
file scripts/tests/release-workflow.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/release-workflow.test.js
per-file results (P=pass F=fail I=infra-exit, one letter per run):
scripts/tests/release-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/release-workflow.test.js: P (exit 0)
round 2 · scripts/tests/release-workflow.test.js: P (exit 0)
round 3 · scripts/tests/release-workflow.test.js: P (exit 0)
round 4 · scripts/tests/release-workflow.test.js: P (exit 0)
round 5 · scripts/tests/release-workflow.test.js: P (exit 0)
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. The R11-1 merge gate is covered by the approving maintainer's independent verification at this head; follow-ups F1–F4 are recorded in the Stage 3 comment. ✅
|
🤖 Addressed the latest review feedback (round 5/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 5/100 轮)。改动内容与我反驳保留之处如下: Autofix review round — addressing independent verification feedbackThank you for the deep real-environment verification. This round implements the three actionable findings (F2, F3, F4), supplies the corrected PR description for F1 (editing the PR body itself is a maintainer/workflow action this round cannot perform), and declines F5/F6 as the informational items they are. Commit: F1 — PR description no longer matches the diff — addressed as far as this round canConfirmed: the body describes the abandoned scrub mechanism, a Reviewer Test Plan step for behavior that no longer exists, a stale test count, and omits several shipped pieces. This round has no PR-body write access, so below is a paste-ready corrected description for the maintainer to apply. Corrected PR description (paste-ready)Motivation The release lane's validation jobs ( Changes
How to verify
F2 — root-owned leftover directory wedges the lane on a bare
|


























What this PR does
Routes the four non-publishing Linux jobs in the release pipeline (
prepare,quality,integration_none, andintegration_docker) through the existing conditional ECS runner selection. ForQwenLM/qwen-code, they use["self-hosted", "linux", "x64", "ecs-qwen"]unlessMAINTAINER_ECS_RUNNER_DISABLED=true; otherwise they fall back toubuntu-latest.The credential-bearing
publishjob and the failure notifier remain onubuntu-latest. Before checkout, the routed jobs wipe the persisted workspace and HOME state; the global Git-config scrub now fails closed if a dangerous exec key cannot be removed or verified. The Audio Capture Prebuilds reusable workflow keeps its existing platform matrix.Why it's needed
During the v0.22.1 release attempt on 2026-08-25, hosted-runner congestion left the pipeline queued while the ECS pool had idle capacity. Moving the long-running validation jobs removes that queue dependency without moving release credentials onto the shared PR runner pool.
Reviewer Test Plan
How to verify
prepare,quality,integration_none, andintegration_dockeruse the conditional ECS expression.publishandnotify_failureare pinned toubuntu-latest.MAINTAINER_ECS_RUNNER_DISABLED=trueselects the hosted fallback for the four routed jobs.Evidence (Before & After)
N/A (workflow-only change).
Tested on
release-workflow.test.js: 23 passed, including the.gitconfig.lockfail-closed regression.Risk & Scope
ecs-qwenpool. The workspace wipe and fresh Git/npm/Docker/gh config directories isolate job-owned filesystem state, andMAINTAINER_ECS_RUNNER_DISABLED=truerestores the hosted fallback._work/_actionsbundles, runner-owned binaries, and the shared Docker daemon remain outside this workflow’s wipe boundary and may survive into a release-validation job.GITHUB_TOKEN;quality,integration_none, andintegration_dockeralso receiveOPENAI_*secrets. Publishing credentials, npm OIDC,CI_BOT_PAT, and failure notification remain on ephemeral hosted runners.Linked Issues
N/A