Skip to content

fix(release): block the transport-timeout pass-through on any exception header - #10902

Merged
wenshao merged 8 commits into
mainfrom
fix/release-guard-any-exception-header
Sep 6, 2026
Merged

fix(release): block the transport-timeout pass-through on any exception header#10902
wenshao merged 8 commits into
mainfrom
fix/release-guard-any-exception-header

Conversation

@yiliang114

@yiliang114 yiliang114 commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

The workspace_tests step may exit 0 over a Vitest worker-RPC timeout, but only with proof the run reached its end and nothing else broke. The last leg of that proof read crash headers out of the log:

&& ! grep -E '^[[:space:]]*Error:' "${log}" | grep -qv 'Timeout calling'

No reading of headers can be complete. Node prints an unhandled exception under its own class header, and that class is producer-chosen — TypeError:, AssertionError [ERR_ASSERTION]:, DOMException:, and equally PoolTimeout:, which carries no Error/Exception suffix at all. A crash beside the transport line cleared all four legs and shipped as a green release behind an annotation asserting that no other error was reported.

This PR stops inferring and reads the number Vitest already publishes. The reporter prints Errors N errors whenever unhandled errors occurred, so the guard sums that across the log and compares it with how many carried the transport's own message:

errors=$(awk '/^[[:space:]]*Errors[[:space:]]+[0-9]+ errors?$/ { total += $2 } END { print total + 0 }' "${log}")
timeouts=$(grep -cE '\[vitest-worker\]: Timeout calling' "${log}" || true)&& [ "${errors}" -eq "${timeouts}" ]

Equal means every unhandled error Vitest counted was its own RPC giving up. No class names, no enumeration, and nothing extra to run.

Two pre-existing defects left the guard unreachable in production. The count is meaningless without them, so both are fixed here:

  • The step did not run with pipefail. GitHub's default for run: is /usr/bin/bash -e {0}, so npm … | tee "${log}" yields tee's status, the || { … } handler never fires, and a shard with a genuinely failing test exits 0 — the release lane's workspace-test gate blocks nothing today. Observed on release run 33806806226, job 100824085040, whose log reports the shell. shell: bash restores -o pipefail, and the step's comment claiming pipefail was already the default is corrected.
  • The log is ANSI-coloured. Vitest colours from the mere presence of CI; release.yml sets neither NO_COLOR nor anything else that stops it, while ci.yml sets NO_COLOR on three steps. A coloured summary puts escapes between a label and its value (ESC[2m Tests ESC[22m ESC[1mESC[32m394 passed), so on real release bytes three of the four conditions return 0 and the fourth never executes. NO_COLOR: 'true' makes every condition — old and new — actually see the log.

Why it is needed

This is the silent-green class #10805 exists to surface, reintroduced by that PR's own pass-through: the review that caught it landed at 08:50 and #10805 merged at 08:49:50, so the finding never gated it.

The earlier commits on this branch widened the header pattern instead (*Exception:, digit- and $-bearing names, Node's coded Name [ERR_CODE]: form). That closed the demonstrated shapes but not the class, which is what R1-1 says: the entrance space is unbounded. A census of this repository finds 26 of 293 Error subclasses (9 %) with suffix-less names, four of which assign that bare name to err.name — the token Vitest prints as the header: GitPullFailure, ChannelLivenessFailure, ProbeRunFailure, SubmitRefusal. The count sees all of them, because it never looks at a header.

Re-running the shard would also settle it, and this branch tried that. It does not fit the lane: timeout-minutes is 45 and the job's own comment records the same third running 6.7 min quiet and 36 min contended, with 45 killing shards at the boundary. Contention is what produces these transport deaths, so the one case a re-run must serve is the case where a second run cannot fit inside the remaining budget — and a job killed by its timeout prints no annotation at all. The count costs nothing and is available in every case.

One more thing the count fixes that no header pattern could: ordinary Error: lines are test output, not evidence. The production log this guard was written for (run 33713579913) carries three of them as fixture data (Error: boom, Error: Unsupported mode "midnight"…, Error: Not implemented: navigation…), each of which defeats the old leg and reddens the release the guard exists to save.

Reviewer Test Plan

Extract the step's run: block by YAML parse and execute it under bash -e -o pipefail with npm stubbed — the harness scripts/tests/release-workflow.test.js already uses. 18 rows:

row exit why
FAIL line 1 a failing test names itself
transport timeout, tally, Errors 1 error 0 pass-through preserved
four transport deaths, Errors 4 errors 0 the production shape (run 33713579913)
two workspaces, both lost to the transport 0 whole-file sums
Error: lines a test printed, Errors 1 error 0 fixture output is not a break
Timeout calling beside a real transport death 0 the count is anchored on [vitest-worker]:
PoolTimeout: beside the timeout, Errors 2 errors 1 R1-1: the shape no enumeration reaches
bare string throw (Unknown Error:), Errors 2 errors 1 ditto
Error: write after end, Errors 2 errors 1 unchanged
tally, then a later workspace crashing 1 a tally cannot cover a later crash
no Errors summary at all 1 absent evidence refuses the pass
Errors 1 error with no tally 1 the run never reached its end
failing tally / passing + failing tallies 1 unchanged
Timeout calling printed by a test, no transport 1 routed to unexplained, not passed through
signal death (137) 137 unchanged
unexplained 7 unchanged

Mutation, run against the same extracted step: dropping the count comparison, making the sum keep only the last summary, unanchoring either the branch or the count, dropping the passing-tally, failing-tally or signal leg, and narrowing the summary pattern to the singular error8 of 8 mutants die, each to its own row.

Risk & Scope

  • Scope is one step: two lines of step configuration, one condition replaced, plus rows. Nothing else in the workflow changes.
  • shell: bash changes release behaviour, deliberately. The gate does not block today; after this it does. A shard with a genuinely failing test will stop a release that would previously have published. That is the point of the step, but it is a live change to the lane and worth a maintainer's eyes rather than mine.
  • What the count still cannot see. Vitest interpolates an unhandled error's .message — only the message, never the class — into Timeout calling "onUnhandledError" with "…". When the RPC that was reporting a crash is the thing that timed out, the crash is never counted, so Errors and the transport count agree and the run passes through. No rule over the log can close that, because the evidence never arrived; only re-executing the shard would, which is the cost the lane's 45-minute budget cannot fund. Ranked by observability this is the worst remaining shape, and it is unchanged from main.
  • Two mutants that survive are pre-existing and not this leg: the ^[[:space:]]* anchor is unpinned (its direction is safe — unanchored is strictly broader), and loosening leg 2 from Tests[[:space:]]+[0-9]+ passed to Tests is invisible to both suites.
  • Not validated: no release was run end to end. Verification is the step extracted verbatim from this workflow with npm stubbed; no Vitest was executed here, and the Errors N errors shape is taken from the reporter's own source (padSummaryTitle('Errors') + ${n} error${n > 1 ? 's' : ''}) rather than from a coloured production log in this environment. The ANSI and pipefail findings are reproductions of measurements posted in this thread, not fresh ones.

Linked #10805

…on header

The workspace-tests step may exit 0 over a Vitest worker-RPC timeout, but only
with proof the run reached its end and nothing else broke. The last leg of that
proof anchored on a bare `Error:`, and Node prints an unhandled exception under
its own class header — `TypeError:`, `AssertionError:`, `SyntaxError:` — so any
crash that is not literally `Error:` cleared all four legs and shipped as a
green release, behind an annotation claiming no other error was reported.

Reproduced against the step's own script with npm stubbed: a log carrying the
transport timeout, a `TypeError: Cannot read properties of null` with a stack,
and a passing tally exits 0 on main and 1 with this change. Widening the header
match to `[A-Za-z_$]*Error:` also covers the whole-file/per-run gap the same leg
had — a passing tally from one workspace no longer covers a later crash, since
that crash's header is now seen.

Two red probes pin both shapes. The legitimate pass-through (timeout plus a
passing tally, nothing else) is unchanged, as are the signal-death, `FAIL`-line
and `Error: write after end` paths.

Found by qwen-code-dev-bot reviewing #10805, which merged ~10 seconds before
that review landed, so the finding never gated it.

Claude-Session: https://claude.ai/code/session_01AWWgJEqafyAT1Mc75T8N7h
@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

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

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Thanks for the PR! Re-run at dc4e64c — the head moved from 4d4ce9e since my last pass, and the two follow-ups I deferred on are now in the diff.

Template — substantively complete, so I'm passing it, but naming the letter of it: ## Why it is needed where the template says ## Why it's needed, no ## Linked Issues heading (Linked #10805 sits as plain text — correctly without a closing keyword, which is what the template asks for), and the ### How to verify / ### Evidence (Before & After) / ### Tested on subsections are absent, their content arriving as prose and an 18-row table instead. Every piece of information the template exists to collect is here. My previous run passed this too, so blocking on heading shapes now would be noise, not gatekeeping.

Problem — observed, not theoretical, and the evidence is unusually concrete. Two named production artefacts: release run 33806806226 / job 100824085040 for the missing pipefail, and run 33713579913 for the four transport deaths beside three fixture Error: lines. I checked the load-bearing structural claim myself rather than taking it on faith: release.yml has no defaults: block anywhere, and shell: 'bash' at line 547 is the only shell: key in the file — so GitHub's bash -e {0} really was the shell, npm … | tee really did report tee's status, and the entire || handler really was dead code. A release gate that blocks nothing.

Direction — aligned. This is release-pipeline integrity, not new surface: it makes an existing gate able to fail, and it stops that gate from false-reddening on test output. No counterpart in the upstream CHANGELOG (pipefail, vitest, transport timeout, worker rpc, CI gate all come up empty), which is expected for internal release tooling and is not a mark against it.

Size — not applicable. Neither file is on a core path (.github/workflows/release.yml, scripts/tests/release-workflow.test.js); no packages/ file is touched, so no Stage 0 tier and no escalation. 58 changed lines in the workflow, 185 in the test file, 243 total — well under both size advisories.

Approach — minimal, and the three parts interlock rather than padding. Dropping any one defeats the other two: without shell: bash the guard never runs, without NO_COLOR every anchored pattern reads zero on coloured bytes, and without the count leg the header matcher fails open on suffix-less classes while false-reddening on fixture output. I tried to find an 80% version and there isn't one. Scope discipline is good — nothing unrelated rode along. The one thing I'd flag as a genuine question, not a blocker: the comment volume is high relative to the logic (~30 lines of prose for ~10 lines of shell). I think it's earned here specifically because shell: 'bash' looks redundant to a future reader and deleting it silently disables the gate — the comment plus the new YAML assertion are what stop that. Worth a moment's thought before it becomes the house pattern elsewhere.

Risk — no elevated risk signals; Stage 1e matched none of the high-revert-correlation paths. The real risk is elsewhere and the PR names it honestly: shell: bash is a live behavioural change to the release lane. A shard with a genuinely failing test starts blocking releases that would previously have published. That is the entire point of the step, but it is the line a maintainer should be signing, not the regex.

Moving on to code review. 🔍

中文说明

感谢贡献!本次为 dc4e64c 上的复审——自我上次审查后 head 已从 4d4ce9e 推进,我当时上交维护者的两条后续项现已进入 diff。

模板——实质内容完整,因此通过,但把字面差异点明:## Why it is needed(模板写的是 ## Why it's needed);没有 ## Linked Issues 标题(Linked #10805 以纯文本出现——并且刻意不带关闭关键字,这正是模板要求的写法);### How to verify / ### Evidence (Before & After) / ### Tested on 三个子标题缺失,其内容以散文加一张 18 行表格的形式给出。模板要收集的信息一项不缺。我上一轮也已判定模板通过,所以现在拿标题形状去卡只会是噪音,而不是把关。

问题——已观测,不是理论性加固,且证据异常具体:两个点名的生产构件,release run 33806806226 / job 100824085040(缺 pipefail),以及 run 33713579913(4 条传输超时死亡,旁边还有 3 行 fixture 打印的 Error:)。最关键的那条结构性论断我自己查证过,没有直接采信:release.yml任何位置都没有 defaults:,第 547 行的 shell: 'bash' 是全文件唯一的 shell: 键——所以当时用的确实就是 GitHub 默认的 bash -e {0}npm … | tee 确实返回的是 tee 的状态,整个 || 处理块确实是死代码。一个什么都拦不住的发版门禁。

方向——对齐。这是发版流水线的完整性,不是新增功能面:它让一个既有门禁第一次能够失败,同时避免该门禁被测试输出误判成红。上游 CHANGELOG 里没有对应条目(pipefailvitesttransport timeoutworker rpcCI gate 均无命中),这对内部发版工具是预期结果,不构成扣分项。

规模——不适用。两个文件都不在核心路径上(.github/workflows/release.ymlscripts/tests/release-workflow.test.js),未触及任何 packages/ 文件,因此不触发 Stage 0 任一层级,也不需要上交。workflow 改动 58 行,测试文件 185 行,合计 243 行——远低于两条规模建议阈值。

方案——最小化,且三部分互相咬合而非堆砌。去掉任何一个,另外两个都失效:没有 shell: bash,guard 根本不会执行;没有 NO_COLOR,所有锚定模式在带色字节上匹配数为零;没有计数那条腿,头部匹配既会在无后缀类名上放行,又会因 fixture 输出误红。我尝试找一个「砍掉 80%」的版本,没有。范围控制得当——没有夹带无关改动。唯一想作为真诚提问(不是阻塞项)提出的:注释相对逻辑量偏多(约 30 行散文对约 10 行 shell)。我认为在这里是值得的,恰恰因为 shell: 'bash' 在后来人眼里像是冗余的,删掉它就会静默废掉整个门禁——那段注释加上新增的 YAML 断言正是防止这件事的东西。但在它成为别处的通用写法之前,值得先想一下。

风险——无升级风险信号;Stage 1e 未命中任何高回滚相关性路径。真正的风险在别处,而且 PR 自己如实点明了:shell: bash发版通道上的一处线上行为变更。真有测试失败的分片从此会拦住原本会照常发布的版本。这正是这个 step 存在的意义,但这才是维护者该签字的那一行,而不是那个正则。

进入代码审查 🔍

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

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code review

My independent proposal, written before I opened the diff. Reading only the title and the "Why it is needed" section: the defect is an allowlist over exception headers, and allowlists over producer-chosen strings cannot be completed — so I would stop reading headers and read a number Vitest already publishes. Its default reporter prints an Errors N errors summary whenever unhandled errors occurred; sum that across the log and compare it with how many lines carried the transport's own message. Equal means every unhandled error Vitest counted was its own RPC giving up. Two preconditions follow necessarily: the log has to be greppable, so colour must be off (NO_COLOR); and the pipeline has to propagate failure at all, so the step needs pipefail, which GitHub's default bash -e {0} does not give you. That is three edits and nothing else.

The diff is that proposal. I have no simpler path to offer and no alternative that survived contact with it, so what follows is verification rather than redirection.

No Critical findings. I read the guard line by line against the head version of the file and tried to break it:

  • The awk pattern ^[[:space:]]*Errors[[:space:]]+[0-9]+ errors?$ splits correctly on default whitespace, so $2 is the count; errors? covers both singular and plural; the trailing $ is what keeps a test's own Errors 2 errors occurred in fixture data out of the sum, and END { print total + 0 } guarantees a numeric value even on a log with no summary at all — which yields 0 against a timeouts of at least 1, so the pass is refused, not granted. That is the right direction for absent evidence.
  • timeouts=$(grep -cE … || true) is safe under -e: the || true keeps the substitution's status 0 while still capturing grep's printed 0.
  • [ "${errors}" -eq "${timeouts}" ] cannot see a non-numeric operand from either side. Were one somehow empty, [ returns 2, the && chain fails, and the pass-through is refused — again failing closed.
  • Narrowing the branch entry from Timeout calling to \[vitest-worker\]: Timeout calling is strictly safer: a log carrying only the bare words now falls to the else arm, gets an ::error annotation, and re-raises the original status via exit "${status}".
  • Both bracket escapes are correct for their mode — BRE in the grep -q, ERE in the grep -cE.
  • The step ends with exit "${status}", so the child's status is re-raised untouched on every non-pass-through path. No reading of the log turns a failure green except the one deliberate branch.
  • shell: 'bash' newly enables pipefail for the whole step, so I checked for collateral: the step body contains exactly one pipeline (npm … | tee). Everything else is grep -q inside if/elif conditions (exempt from -e), command substitutions already carrying || true, echo, and exit. No new failure surface beyond the intended one.
  • NO_COLOR: 'true' is correctly quoted, so YAML yields the string rather than a boolean, and the new test assertion pins exactly that.

Reuse check — nothing new was invented where something existed. NO_COLOR is the same convention ci.yml already applies at three steps (lines 693, 1565, 1737 — I confirmed all three); this PR makes the release lane agree with the PR lane rather than adding a parallel mechanism. The awk/grep idioms are the ones six other workflows in this repo already use.

Non-blocking, worth a look:

  1. Errors == timeouts compares a summary count against a line count. They are equal only while one transport death produces exactly one line carrying the message. If that ever stops holding, the guard refuses a pass-through it earned — a false red, which is the safe direction, but it is the same failure mode this PR exists to remove, so it is worth knowing it is still reachable in principle.
  2. The annotation text says "Every test passed" on a shard whose tally reported fewer tests than it collected. The discriminator is sitting in the log (Test Files 1 passed (2)) and leg 2 does not read it.
  3. The test row commented "the summary sum is anchored on the section-line shape" is actually discriminated by the trailing $, not the leading ^[[:space:]]* anchor. Comment fix, not code.
  4. Both 2 and 3 are already on the thread from the maintainer's round-2 verification; I'm repeating them so they don't get lost when this comment replaces my earlier one.

The residual gaps are real and are not regressions. Two pass-through routes remain open: a workspace that dies before printing any summary, and a transport timeout that costs a whole test file its results. On main both behave identically — they are pre-existing, not introduced here. What is new is that they become reachable, because before this PR the pass-through could not fire at all and the step could not fail at all. I weigh that as a clear net gain: main today exits 0 over a genuinely failing test in every shard, which dominates both routes. A validated fifth leg that closes them is already written up in the thread and belongs in a follow-up with its own probes, not in this diff.

One honest caveat I could not resolve by reading. The count is not a strict superset of the header rule. A pre-summary crash whose header happens to be exactly Error: is caught by main's leg 4 and passed through by the count. That is a regression against main's intended rule but not against its behaviour, since on main that path is unreachable in production for both of the reasons this PR fixes. I'm noting it because it is the one place where this diff is weaker than what it replaces, and a reader should not have to find it themselves.

Test evidence

This is an unattended CI run (GITHUB_EVENT_NAME=issue_comment), so per the gate's rules I did not build or execute anything from this PR — no npm, no node, no checkout, no applying the diff. Everything below is the PR's own CI, read through the API for the reviewed commit, plus static reads of the workflow at that same commit.

All 409 check-runs on dc4e64c are settled — 0 failures, 0 pending. Substantive CI is green:

Check Conclusion
Test (ubuntu-latest, Node 22.x) success
Lint & Static (ubuntu-latest, Node 22.x) success
Integration Tests (no-AK, No Sandbox) success
build-cli success
Desktop Shell (ubuntu-22.04) success
Desktop Shell (windows-2022) success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) success
Classify PR success
Bot orchestration (route ×6, review-pr, review-scan, review-address, assign, label, authorize, delay-automatic-review, Remind on force-push) success
379 further matrix/orchestration checks skipped
route ×8 (superseded orchestration attempts) cancelled

Reading that signal rather than just reporting it green:

  • The changed test file is genuinely covered. scripts/tests/release-workflow.test.js runs via npm run test:scripts, which ci.yml invokes inside the Run tests and generate reports step (line 759) of the Test job — the check that is green above. I traced that instead of assuming a workflow change was only linted. So the 47-row suite that pins the extracted step body really did execute on this commit.
  • The 8 cancelled checks are infra noise, not PR-caused. All eight are the route orchestration job from superseded issue_comment runs of the review bot, and six further route attempts on the same commit succeeded. Nothing in this PR touches those workflows, and no substantive check is among the cancellations.
  • The 379 skips are the normal matrix shape for this repo's conditional workflows, not a coverage hole.

What CI proves, and what it does not. The green Test check proves the extracted step body returns the right exit code across all 47 synthetic log shapes, and that dropping any constituent of the guard turns a row red. It cannot prove that real Vitest emits Errors 1 error in the shape the awk pattern expects, nor that --workspaces mode does not also print an aggregate summary that would double the sum. That is the whole behavioural claim of the PR, and it rests on measurements posted in this thread, not on anything I re-ran: the author's description and a repo admin's two rounds of local verification (real Vitest 3.2.7, real npm run --workspaces, and the real 987,795-byte production log from run 33713579913, de-coloured — on which the sum came out 4 errors against 4 transport lines, and passed + skipped == collected held on all 20 summaries, which is what rules out the double-count). I'm attributing that clearly as their measurement. Not verified by me: real Vitest output shape and real runner pipefail behaviour — I cannot execute PR code on this path.

Sandboxed verification would settle exactly that: @qwen-code /verify — that Vitest's own summary really prints the Errors N errors line the awk pattern anchors on, that --workspaces prints one such line per workspace and no aggregate that would inflate the sum past the transport count, and that shell: bash really does flip a genuinely failing shard from exit 0 to exit 1 on a live runner. None of those three are observable from the diff, and this PR's suite passes on synthetic logs either way. Note that the last substantive /verify on this thread (run 33901913805, 2026-09-04) predates the current head and returned findings that this diff then addressed — 438 scripted assertions passed and the flakiness gate was clean, but a fresh run at dc4e64c is what would close the gap.

Real-scenario tmux testing: N/A. This is an unattended CI run, and the change is a release-workflow shell guard with no TUI surface to drive. On this path the live-behaviour signal comes from the sandboxed lane named above, not from a local tmux session.

中文说明

代码审查

我在打开 diff 之前写下的独立方案。 只读标题和「Why it is needed」:这个缺陷是一张针对异常头部的白名单,而针对「由抛出方自选的字符串」的白名单不可能穷尽——所以我会不再去读头部,改读 Vitest 自己已经公布的数字。它的默认 reporter 在出现未处理错误时会打印 Errors N errors 摘要;把整份日志里的这个数求和,再与携带传输层自身消息的行数比较。相等即意味着 Vitest 计入的每一个未处理错误都是它自己的 RPC 放弃。由此必然推出两个前提:日志必须可被 grep,所以颜色必须关掉(NO_COLOR);管道必须真的能传递失败,所以这个 step 需要 pipefail,而 GitHub 默认的 bash -e {0} 不给。就三处改动,别无其他。

diff 就是这个方案。我没有更简的路径可提,也没有任何替代方案经得起推敲,所以下面是核验,而不是改向。

无 Critical 发现。 我对着该 commit 上的文件逐行读了这条 guard,并尝试打破它:

  • awk 模式 ^[[:space:]]*Errors[[:space:]]+[0-9]+ errors?$ 在默认空白分隔下切分正确,$2 就是计数;errors? 同时覆盖单复数;真正挡住测试自己打印的 Errors 2 errors occurred in fixture data 的是尾锚 $;而 END { print total + 0 } 保证即使日志里完全没有摘要行也会输出数值——此时得到 0,对上至少为 1 的 timeouts,于是放行被拒绝而非授予。证据缺失时方向正确。
  • timeouts=$(grep -cE … || true)-e 下是安全的:|| true 让替换的退出状态为 0,同时仍然捕获 grep 打印的 0
  • [ "${errors}" -eq "${timeouts}" ] 两侧都不可能拿到非数值。万一为空,[ 返回 2,&& 链失败,pass-through 被拒——同样是失败关闭。
  • 把分支入口从 Timeout calling 收窄到 \[vitest-worker\]: Timeout calling 严格更安全:只带这几个裸词的日志会落到 else 分支,拿到 ::error 注解,并通过 exit "${status}" 原样重抛子进程状态。
  • 两处方括号转义在各自模式下都正确——grep -q 用 BRE,grep -cE 用 ERE。
  • step 以 exit "${status}" 收尾,因此所有非 pass-through 路径都原样重抛子进程状态。除了那一条刻意保留的分支,任何对日志的解读都无法把失败变绿。
  • shell: 'bash' 为整个 step 新引入了 pipefail,所以我查了连带影响:step 正文里只有一条管道(npm … | tee)。其余都是 if/elif 条件里的 grep -q(不受 -e 约束)、已带 || true 的命令替换、echoexit。除预期那一处外没有新增失败面。
  • NO_COLOR: 'true' 引号正确,YAML 解析出的是字符串而非布尔值,新增的测试断言钉的正是这一点。

复用检查——没有在已有实现的地方另造轮子。NO_COLOR 就是 ci.yml 已在三个 step 上使用的同一约定(693、1565、1737 行,我逐一确认过);本 PR 是让发版通道与 PR 通道达成一致,而不是新增一套并行机制。awk/grep 写法也是本仓库另外 6 个 workflow 已在用的那些。

非阻塞,值得看一眼:

  1. Errors == timeouts 比的是摘要计数行数。只有当一次传输死亡恰好产生一行携带该消息时二者才相等。一旦这个前提不再成立,guard 会拒绝一次它本该给的放行——即误红。方向是安全的,但这正是本 PR 要消除的失败形态,所以值得知道它在原则上仍然可达。
  2. 在 tally 报出的测试数少于收集数的分片上,注解仍写「Every test passed」。判别信息就在日志里(Test Files 1 passed (2)),而第 2 条腿不读它。
  3. 那行注释写着「summary 求和锚定在小节行形态上」的用例,实际区分它的是尾锚 $,不是首锚 ^[[:space:]]*。改注释即可,不必改代码。
  4. 第 2、3 两条在维护者的第 2 轮验证里已经提过;我在此重复,是为了这条评论替换掉我先前那条时它们不会丢失。

残留缺口是真实的,但不是回归。 仍有两条 pass-through 路径敞开:一个在打印任何摘要之前就死掉的 workspace,以及一次让整个测试文件结果丢失的传输超时。在 main 上两者表现完全一致——属于既有问题,不是本 PR 引入。真正的地方是它们变得可达了,因为在本 PR 之前 pass-through 根本不会触发,整个 step 也根本不会失败。我的权衡是净收益明确:main 今天会在每一个真有测试失败的分片上退出 0,这个后果压过上述两条路径。一条已验证、能同时关掉两者的第五条腿已在讨论串里写好,它该进一个带自己探针的后续 PR,而不是塞进这份 diff。

一处我靠阅读无法消解的坦白说明。 计数并不是头部匹配规则的严格超集。一个在打印任何摘要之前就崩溃、且头恰好是 Error: 的 workspace,会被 main 的第 4 条腿拦下,却被计数放行。这是相对 main 设计意图的回归,不是相对其实际行为的回归——在 main 上这条路径因本 PR 修的那两个原因在生产中根本不可达。我把它写出来,是因为这是本 diff 唯一一处弱于它所替换之物,读者不该自己去发现。

测试证据

这是一次无人值守的 CI 运行(GITHUB_EVENT_NAME=issue_comment),因此按门禁规则我没有构建或执行本 PR 的任何东西——没有 npm、没有 node、没有 checkout、没有应用 diff。下面全部内容都是通过 API 读取的该 PR 自身 CI(针对被审 commit),外加对同一 commit 上 workflow 文件的静态阅读。

dc4e64c 上全部 409 个 check-run 均已结束——0 失败、0 待运行。实质性 CI 全绿(表格见英文部分,此处不复述)。

对这个信号的解读,而不只是报告它绿了:

  • 被改动的测试文件确实被覆盖。 scripts/tests/release-workflow.test.js 通过 npm run test:scripts 运行,而 ci.yml 是在 Test job 的 Run tests and generate reports step 里调用它的(第 759 行)——也就是上表中那个绿色的 check。我是追踪确认的,没有假设一处 workflow 改动只会被 lint。因此那套钉住提取出的 step 正文的 47 行用例,确实在这个 commit 上执行过。
  • 8 个被取消的 check 是基础设施噪音,不是本 PR 造成的。 八个全是 review bot 被后续运行取代的 route 编排 job(issue_comment 触发),同一 commit 上另有六次 route 成功。本 PR 未触及这些 workflow,且被取消的里面没有任何实质性 check。
  • 379 个跳过是本仓库条件式 workflow 的正常矩阵形态,不是覆盖空洞。

CI 证明了什么,以及没有证明什么。 绿色的 Test check 证明:提取出的 step 正文在全部 47 种合成日志形态下都返回了正确的退出码,且删掉 guard 的任何一个组成部分都会让某一行变红。它无法证明真实 Vitest 会输出 awk 模式所期望的 Errors 1 error 形态,也无法证明 --workspaces 模式不会额外打印一个聚合摘要、从而把求和撑到超过传输行数。而这正是本 PR 的全部行为性论断,它依赖的是讨论串里 posted 的实测,不是我重跑的结果:作者的描述,以及一位仓库 admin 的两轮本地验证(真实 Vitest 3.2.7、真实 npm run --workspaces,以及来自 run 33713579913 的真实 987,795 字节生产日志去色后的版本——在其上求和结果为 4 个错误对 4 条传输行,且全部 20 个摘要都满足 passed + skipped == collected,这正是排除重复计数的依据)。我把这些明确归属为他们的实测。未由我验证:真实 Vitest 的输出形态,以及真实 runner 上的 pipefail 行为——在这条路径上我不能执行 PR 代码。

沙箱验证恰好能了结这一点:@qwen-code /verify——用来确认 Vitest 自己的摘要真的会打印 awk 所锚定的 Errors N errors 行;确认 --workspaces 每个 workspace 只打印一行、且没有会把求和撑过传输计数的聚合摘要;确认 shell: bash 真的能让一个确有测试失败的分片在真实 runner 上从退出 0 翻成退出 1。这三点都无法从 diff 观察,而本 PR 的套件在合成日志上无论如何都是通过的。需要说明的是,本讨论串上最近一次实质性 /verify(run 33901913805,2026-09-04)早于当前 head,其报告的发现已被这份 diff 处理——当时 438 条脚本断言全过、抖动门干净,但只有在 dc4e64c 上重跑一次才能真正补上缺口。

真实场景 tmux 测试:N/A。 这是无人值守的 CI 运行,且改动是发版 workflow 里的 shell guard,没有可驱动的 TUI 界面。在这条路径上,实时行为信号来自上面点名的沙箱通道,而不是本地 tmux 会话。

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

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — the engineering is right and I have no simpler path to offer; the one point I'm holding back is that the central behavioural claim rests on measurements posted in this thread rather than anything I could re-run on this path.

Going back to my independent proposal: it was this diff, written down before I opened it. That is the strongest signal I have that the approach is correct rather than merely defensible — I arrived at "stop reading headers, read the count Vitest publishes, and fix the two preconditions that make the count legible" from the problem statement alone, and the PR got there by the same route with the same three edits. So there is no simpler path I'm withholding.

What changed my posture since my last pass at 4d4ce9e. I deferred then, at 3/5, for two reasons: a Critical stood unresolved at that exact commit — the guard still failed open on a header with no Error/Exception suffix, which this repo really does ship (GitPullFailure, ChannelLivenessFailure, ProbeRunFailure, SubmitRefusal) — and the open question was release-throughput policy, which is a maintainer's call rather than a gate's. Neither survives this head. The Critical is not argued away, it is structurally dissolved: the count never looks at a header, so there is no enumeration left to be incomplete. And the policy question was answered by the person who owns it, in the affirmative, on this commit.

On the seat I'm deciding from, which is what actually stopped me last time: I would be adding a second approval beside a maintainer's, on main's two-approval rule. I checked whether that vote is mine to give rather than assuming it. My own prior APPROVED review sits on 1a84c6c4, an older commit, and branch protection dismisses stale reviews — so it carries nothing here, and the standing CHANGES_REQUESTED at this head came from the review lane disclosing that it ran out of tool budget, not from a confirmed blocker. wenshao's approval at dc4e64c is a different vote, not mine, and I did not read it as settling anything on my behalf.

Did I verify the problem exists rather than accept the framing? The structural half, yes, myself: release.yml has no defaults: block anywhere and shell: 'bash' is its only shell: key, so GitHub's bash -e {0} really was the shell, npm … | tee really did return tee's status, and the whole || handler really was dead code. A release gate that blocks nothing is not a hypothetical. The NO_COLOR half I corroborated from the repo rather than the description — ci.yml already sets it at lines 693, 1565 and 1737 on the steps that run these same suites on every PR, so the release lane was the outlier and this makes two lanes agree. What I could not verify myself is real Vitest's output shape, because I don't execute PR code on this path; I said so plainly in Stage 2 and named the lane that would close it.

Is every change necessary? Yes, and they interlock — I looked for an 80% version and there isn't one. Drop shell: bash and the guard never runs; drop NO_COLOR and every anchored pattern reads zero on coloured bytes; drop the count and you are back to an allowlist that cannot be completed. The 185 test lines are not padding either: each constituent of a fiddly regex has a row that goes red when it is removed, and the two new YAML assertions are what stop a future reader deleting shell: 'bash' as redundant.

If I inherit this in six months I will thank whoever wrote it. The thing I would curse is a comment convention drifting toward thirty lines of prose per ten lines of shell — but here it is load-bearing, because the line it explains looks redundant and silently is not.

My reservations, named so they are on the record rather than buried:

  1. Two pass-through routes stay open and become reachable — a workspace that dies before any summary, and a transport timeout that costs a whole test file its results. Pre-existing on main byte-for-byte, and dominated by what this fixes (main exits 0 over a genuinely failing test in every shard today). The validated fifth leg that closes both is already written up in the thread; it should be a tracked follow-up with its own probes, and I'd ask that it not be silently dropped.
  2. One place where this diff is weaker than what it replaces: a pre-summary crash whose header is exactly Error: is caught by main's leg 4 and passed through by the count. A regression against main's intended rule, not its behaviour — that path is unreachable in production on main for both reasons this PR fixes. Worth a maintainer's eye because it is the only such place.
  3. shell: bash is the line being signed, not the regex. The workspace-test gate starts blocking releases. That is the point of the step, and it is a deliberate live change to the release lane.
  4. Cosmetic: the annotation says "Every test passed" on a shard whose tally reported fewer tests than it collected, and one test comment attributes a row to the leading anchor when the trailing $ is what discriminates it.

None of these are reasons to hold. Verdict: approve, pinned to the reviewed commit. All 409 checks on dc4e64c are settled with 0 failures and 0 pending, no pull_request workflow run is still in flight, and the change is neither a fork nor a refactor, so no guardrail or deferral applies. The follow-up in 1 is the thing I'd want tracked.

中文说明

信心度:4/5 —— 工程做法是对的,我没有更简的路径可提;唯一让我保留一分的地方是,核心的行为性论断依赖的是本讨论串里 posted 的实测,而不是我在这条路径上能重跑出来的东西。

回到我的独立方案:它就是这份 diff,而且是我在打开 diff 之前写下的。这是我手上最强的信号,说明该方案不只是「说得通」,而是正确——我仅凭问题陈述就得出了「别再读头部,去读 Vitest 自己公布的计数,并修好让这个计数可读的两个前提」,而 PR 走的是同一条路、同样三处改动。所以我没有藏着什么更简的方案。

自我上次在 4d4ce9e 上审查以来,我的立场为何改变。当时我以 3/5 上交,理由有两条:一是该 commit 上有一个 Critical 未解决——guard 在头部不带 Error/Exception 后缀时仍然放行,而本仓库确实存在这类名字(GitPullFailureChannelLivenessFailureProbeRunFailureSubmitRefusal);二是悬而未决的是发版吞吐量策略,那是维护者的判断,不是门禁的。两条在当前 head 上都不成立了。那个 Critical 不是被辩驳掉的,而是被从结构上消解了:计数根本不看头部,于是再没有一张「可能不完备」的白名单。而策略问题由拥有该权限的人给出了肯定答复,就在这个 commit 上。

关于我从哪个位置做判断——这才是上次真正拦住我的东西:我将是在 main 的两票规则下、在维护者旁边补上第二票。我核查了这一票是否该由我投,而不是想当然。我自己先前那条 APPROVED 落在更早的 1a84c6c4 上,而分支保护会 dismiss 过期审查——所以它在这里不带任何效力;当前 head 上那条仍然挂着的 CHANGES_REQUESTED 来自审查通道自陈工具预算耗尽,不是一个已确认的阻塞项。wenshaodc4e64c 上的批准是另一票,不是我的票,我没有把它当作替我做了结论。

我是否核验了问题真实存在,而不是接受其叙事?结构性那一半,我自己查了:release.yml 任何位置都没有 defaults: 块,且 shell: 'bash' 是全文件唯一的 shell: 键,所以当时用的确实就是 GitHub 的 bash -e {0}npm … | tee 确实返回 tee 的状态,整个 || 处理块确实是死代码。「一个什么都拦不住的发版门禁」不是假设。NO_COLOR 那一半我是从仓库里而非描述里取得佐证的——ci.yml 已在 693、1565、1737 行的三个 step 上设了它,而那三步在每个 PR 上跑的就是同一批套件,所以发版通道才是那个例外,这一改让两条通道一致。我无法自己核验的是真实 Vitest 的输出形态,因为我在这条路径上不执行 PR 代码;我在 Stage 2 里直说了这一点,并点名了能补上它的通道。

每一处改动都必要吗?是的,而且互相咬合——我找过「砍掉 80%」的版本,没有。去掉 shell: bash,guard 根本不执行;去掉 NO_COLOR,所有锚定模式在带色字节上读到零;去掉计数,就退回到一张不可能穷尽的白名单。那 185 行测试也不是填充:一个复杂正则的每个组成部分都有一行用例,删掉就变红;两条新增的 YAML 断言则阻止后来人把 shell: 'bash' 当冗余删掉。

如果六个月后由我接手,我会感谢写它的人。会让我骂人的是注释惯例朝着「十行 shell 配三十行散文」漂移——但在这里它是有承重作用的,因为它解释的那一行看着冗余,实则悄悄不是。

我的保留意见,明说出来留在记录上,而不是埋起来:

  1. 两条 pass-through 路径仍然敞开,并且变得可达——一个在打印任何摘要之前就死掉的 workspace,以及一次让整个测试文件结果丢失的传输超时。在 main 上逐字节同样存在,并且被本 PR 修掉的东西压过(main 今天会在每一个真有测试失败的分片上退出 0)。能同时关掉两者的、已验证的第五条腿已在讨论串里写好;它应作为一个带自己探针的后续项被跟踪,我希望它不要被静默丢弃。
  2. 本 diff 有一处弱于它所替换之物:一个在打印任何摘要之前就崩溃、且头恰好是 Error: 的 workspace,会被 main 的第 4 条腿拦下,却被计数放行。这是相对 main 设计意图的回归,不是相对其实际行为的回归——在 main 上这条路径因本 PR 修的那两个原因在生产中不可达。值得维护者过目,因为这是唯一这样的一处。
  3. 要签字的是 shell: bash 这一行,不是那个正则。 workspace 测试门禁从此开始拦截发版。这正是这个 step 的意义,也是对发版通道的一处刻意线上变更。
  4. 细节:在 tally 报出的测试数少于收集数的分片上,注解仍写「Every test passed」;另有一处测试注释把某行用例归给了首锚,而真正区分它的是尾锚 $

这些都不构成压住它的理由。结论:批准,并钉在被审 commit 上。dc4e64c 上全部 409 个 check 均已结束,0 失败 0 待运行,没有 pull_request workflow run 仍在飞行中,且该改动既不是 fork 也不是 refactor,因此不适用任何护栏或延后。第 1 条里的后续项是我希望被跟踪的那件事。

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

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship — CI landed green after the review. ✅

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

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

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

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

Comment thread .github/workflows/release.yml Outdated
The previous widening to `[A-Za-z_$]*Error:` still enumerated header shapes and
left corners open, as the review probe showed:

- Node prints coded internal errors as `Name [ERR_CODE]: message`
  (`AssertionError [ERR_ASSERTION]:`, `Error [ERR_MODULE_NOT_FOUND]:`), with the
  bracketed code between the class name and the colon, so the anchored pattern
  never matched the line.
- The character class omitted digits, and this repo throws a digit-bearing
  class (`LargeNonUtf8TextError`).

Both now match, and `Exception` suffixes are accepted alongside `Error`.
Re-probed against the step's own script with npm stubbed: `AssertionError
[ERR_ASSERTION]:`, `TypeError [ERR_INVALID_ARG_TYPE]:`, `Error
[ERR_MODULE_NOT_FOUND]:`, `LargeNonUtf8TextError:` and the plain `TypeError:`
all flip to exit 1, while a clean transport-timeout log stays exit 0 so the
`transport timeout, run completed` case keeps its pass-through.

This narrows the class rather than closing it: a producer-defined name without
an `Error`/`Exception` suffix (`PoolTimeout:`) still clears the guard, verified
still exit 0. Closing it properly means deciding on an authoritative signal —
vitest's machine-readable report, or re-running the shard and requiring green —
instead of a wider header regex. Recorded here rather than left implicit.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not explored to full depth (tool budget reached): "agent 1d": full vitest run of scripts/tests/release-workflow.test.js at HEAD.

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

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

Comment thread .github/workflows/release.yml Outdated
Comment thread scripts/tests/release-workflow.test.js Outdated
Mutation-checked the committed suite and confirmed the review's finding: three
parts of the widened matcher had no probe, so deleting any one left everything
green while reopening the hole.

  (Error|Exception) -> (Error)      DOMException:            exit 1 -> 0
  [A-Za-z0-9_$] -> [A-Za-z_$]       LargeNonUtf8TextError:   exit 1 -> 0
  [A-Za-z0-9_$] -> [A-Za-z0-9_]     Foo$Error:               exit 1 -> 0

Each new row goes red under its own mutant and green at HEAD, so they pin the
constituent rather than decorate the table. `LargeNonUtf8TextError` is this
repo's own class (`packages/core/src/utils/read-text-range.ts`), not a
hypothetical.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not explored to full depth (tool budget reached): "agent 6a": none. (One check was substituted, not cut: the vitest run was replaced by the shell simulation above because no node_modules is installed in either tree.).

[Critical] R1-1: [certifies-falsely] The pass-through guard still enumerates exception-header shapes, and that entrance space — producer-chosen class names, err.name set to any string, non-Error throws — is unbounded, so the guard fails open: a crash under a header with no Error/Exception suffix (PoolTimeout: or any producer-defined class name) beside the transport-timeout line, a passing tally and exit status 1 clears all four legs and exits 0 behind an annotation claiming no other error was reported — a broken release ships green. Unchanged since round 2 (git diff da29da9..HEAD -- .github/workflows/release.yml is empty); this round's verifier probe at HEAD confirms the PoolTimeout: shape still exits 0. The structural fail-closed fix (option A: vitest's machine-readable report affirming every run passed with no unhandled errors; option B: re-running the affected shard and requiring green) has not landed, and the author has asked the maintainer to choose between them. The fix must keep the clean-timeout row green — scripts/tests/release-workflow.test.js pins exit 0 and the "passed through a Vitest transport timeout" annotation for a clean timeout log — and must not rest on ignoring unhandled errors: scripts/tests/unit-vitest-configs.test.ts:77-83 pins dangerouslyIgnoreUnhandledErrors to false on Linux. Witness: probe on HEAD 4d4ce9e (real step script extracted verbatim from release.yml, bash -e -o pipefail, npm stubbed): ROW clean (timeout + tally only) -> exit 0 "passed through a Vitest transport timeout" (intentional control); ROW typeerror (TypeError: beside the timeout) -> exit 1 "exited 1 on a Vitest transport timeout"; ROW pooltimeout (PoolTimeout: worker pool exhausted beside the timeout + tally) -> exit 0 "passed through ... Every test passed and no other error was reported" (WRONG); ROW barethrow (thrown bare string, no class header) -> exit 0 (also passes through). Flip check against a patched copy broadening the matcher to any identifier-colon header: clean stays exit 0, pooltimeout flips to exit 1. Sweep of the real regex: MATCH TypeError / AssertionError [ERR_ASSERTION] / DOMException / LargeNonUtf8TextError / Foo$Error / AbortError; MISS PoolTimeout / ConnectTimeout / DeadlineExceeded / Cancelled / MyCustomFailure / UND_ERR_CONNECT_TIMEOUT. Fix witness: add a probe row in scripts/tests/release-workflow.test.js with a suffix-less crash header (PoolTimeout: pool exhausted beside the timeout line and passing tally, expecting exit 1) — it stays red against the current regex and must flip green when the structural fix lands, and go red again if that fix is removed.

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

@wenshao

wenshao commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

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: 240 passed · 0 failed · 240 total

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

中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)

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

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

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

Verification report

PR #10902 — deep verification

Verdict: findings — 240 scripted assertions, 240 pass / 0 fail. Verified head 4d4ce9e09a37805dc8455834c7ea0e254055fcf8 (git rev-parse HEAD^2, equal to the snapshot's headRefOid); base tip c8595fc9d85ee348c03db5e1cda10a08b8898414 (HEAD^1).

The change does exactly what it says it does — but the branch it guards cannot be entered in the release lane as configured, so it cannot fire there, and neither could the pass-through it is hardening. That is a finding about the premise, not about the code: nothing here is a reason to reject the diff, and one measured one-line change makes the diff load-bearing.

中文摘要

结论:findings(240 条脚本化断言,全部通过,0 失败)。

A/B 结论:在纯文本日志(guard 分支可达的配置)下,把 ^[[:space:]]*Error: 放宽为 ^[[:space:]]*[A-Za-z0-9_$]*(Error|Exception)( \[[A-Za-z0-9_]+\])?: 确实生效——29 个 fixture 中有 14 个从 base 的 exit 0(放行)翻转为 head 的 exit 1(拦住),包含 PR 自己列出的 6 行;合法放行、信号死亡(137)、FAIL 行、Error: write after end 全部保持不变。变异矩阵 9 个单点变异中 7 个被杀,每个都由它对应的那一行测试杀掉(报错信息为 expected +0 to be 1),M0 未变异对照为绿。

核心发现(F1):release lane 实际产出的日志是带 ANSI 颜色的。本容器实测 CI=truetinyrainbow 的判定是"只要环境里存在 CI 就着色,除非存在 NO_COLOR"。release.yml 两者都没设,而 ci.yml 在 3 个步骤里设了 NO_COLOR: true。实测真实 vitest(含真实 npm run test:ci -w packages/core)管道输出的汇总行是 \x1b[2m Tests \x1b[22m \x1b[1m\x1b[32m47 passed,因此四条 && 串联判据中的第 2 条 ^[[:space:]]*Tests[[:space:]]+[0-9]+ passed 永远不匹配 → 第 4 条(本 PR 放宽的那条)根本不会执行。用 grep 垫片计数证明:真实彩色日志下第 4 条执行 0 次,同一次运行加 NO_COLOR=1 后执行 1 次(阳性对照)。

推论有两个方向:(a) 本 PR 修的洞在真实 lane 里也进不去(base 同样 exit 1),所以"缺陷在 main 上是活的"这一表述不准确;(b) #10805 想要的"传输超时视为通过"也从未生效,release 依然会因超时变红。第 1、3 条判据同样对彩色日志失明,普通测试失败会拿到 with no failing test 这个错误诊断(退出码仍正确,属 fail-closed,不会放出绿色 release)。

建议修法(已实测):给该步骤 env 加 NO_COLOR: true(与 ci.yml 一致)。实测该配置下 vitest 输出 0 个 ESC、第 2 条判据 MATCH、第 4 条能看到 header;再叠加本 PR 的放宽即得到预期行为。

其余 findings:F2 vitest 在 RPC 超时上报未处理错误时会把错误 message 拼进 Timeout calling 那一行(rpc.-pEldfrD.js:52),而那一行正是 guard 主动过滤掉的——真实未处理错误仍可放行,且不在 PR 自认的取舍清单里;F3 三个未被点名的同族形状仍然放行(带点号的 vitest.TypeError:、代码括号前两个空格、代码里带连字符),PR 自认的两个洞(PoolTimeout:、裸字符串 throw)已复现确认;F4 唯一存活变异:匹配器的缩进容忍 ^[[:space:]]* 没有任何一行测试钉住(属覆盖缺口,非死代码)。

未覆盖范围:无法对真实生产产物做校准(本 job 无 GitHub token,且首轮无 previous-report.md);3 个 commit 中仅 1 个本地可达,中间态匹配器是按 commit message 文本重建的;yamllint 装不上(pip3: Permission denied);未跑 typecheck / ESLint / 各 workspace 单测;未产生真实的 vitest worker-RPC 超时(复现的是日志形状,不是停顿成因);自托管 ECS runner 是否导出 NO_COLOR 无法在此测量。

Central claim and scope

Central claim. Widening the exception-header grep in the workspace_tests transport-timeout pass-through — from ^[[:space:]]*Error: to ^[[:space:]]*[A-Za-z0-9_$]*(Error|Exception)( \[[A-Za-z0-9_]+\])?: — stops a crash that Node prints under a non-Error: class header from clearing all four legs and shipping a green release.

Secondary claim 1. The legitimate pass-through and the step's other branches are unchanged (no regression, no new false positive on a clean run).

Secondary claim 2. The six added rows pin each constituent of the matcher, so a later narrowing goes red.

Everything else is out of scope and listed under Not covered.

A/B table

Both arms are the step's run: block extracted verbatim from its own release.yml with the yaml parser (no retyping) and executed under GitHub Actions' documented Linux default, bash --noprofile --norc -eo pipefail {0}. The two extracted scripts differ on exactly one line (sha256 80060eee… base vs d582bc9e… head; diff = the matcher). npm is stubbed; the log is fed as bytes so ANSI and CR fixtures survive. Oracle per cell: the step's exit status plus the exact ::warning/::error annotation title. leg4 counts executions of the header grep, measured by a grep shim placed ahead of /usr/bin/grep — so "the widened leg never ran" is a counted fact, not an inference from &&.

Witness: 01-ab-matrix-base-vs-head.png. Raw: logs/ab-run.log, logs/ab-matrix.log, logs/ab-results.json.

fixture stub base head flip leg4 b/h
A1 pass-through: timeout + passing tally only 1 0 pass-through 0 pass-through 1/1
A2 TypeError: beside the timeout 1 0 pass-through 1 exited-1 YES 1/1
A3 AssertionError [ERR_ASSERTION]: 1 0 pass-through 1 exited-1 YES 1/1
A4 tally, then a later AssertionError: 1 0 pass-through 1 exited-1 YES 1/1
A5 Error: write after end (pre-existing row) 1 1 exited-1 1 exited-1 1/1
A6 signal death 137 137 exited-137 137 exited-137 0/0
A7 FAIL line 1 1 none 1 none 0/0
B1 DOMException: 1 0 pass-through 1 exited-1 YES 1/1
B2 LargeNonUtf8TextError: (digit) 1 0 pass-through 1 exited-1 YES 1/1
B3 Foo$Error: (dollar) 1 0 pass-through 1 exited-1 YES 1/1
B4 Error [ERR_MODULE_NOT_FOUND]: 1 0 pass-through 1 exited-1 YES 1/1
B5 DOMException [AbortError]: (real node bytes) 1 0 pass-through 1 exited-1 YES 1/1
B6 indented TypeError: 1 0 pass-through 1 exited-1 YES 1/1
B7 RangeError: 1 0 pass-through 1 exited-1 YES 1/1
C1 PoolTimeout: — PR-admitted hole 1 0 pass-through 0 pass-through 1/1
C2 bare string throw — PR-admitted hole 1 0 pass-through 0 pass-through 1/1
D1 vitest.TypeError: (dotted, real node bytes) 1 0 pass-through 0 pass-through 1/1
D2 timeout that swallowed the unhandled error 1 0 pass-through 0 pass-through 1/1
D3 Error [ERR_ASSERTION]: (two spaces) 1 0 pass-through 0 pass-through 1/1
D4 Error [ERR-SOMETHING]: (hyphen) 1 0 pass-through 0 pass-through 1/1
D5 CR-prefixed \rTypeError: 1 0 pass-through 1 exited-1 YES 1/1
D6 CRLF log 1 0 pass-through 1 exited-1 YES 1/1
E1 real plain vitest log, clean, + timeout 1 0 pass-through 0 pass-through 1/1
E2 passing test that logs a header-shaped string 1 0 pass-through 1 exited-1 YES 1/1
E3 unrelated exit, no timeout 7 7 no-failing-test 7 no-failing-test 0/0
F1 real colourised unhandled-error log + timeout 1 1 exited-1 1 exited-1 0/0
F2 real colourised clean log + timeout 1 1 exited-1 1 exited-1 0/0
F3 real packages/core colourised log + timeout 1 1 exited-1 1 exited-1 0/0
F4 the same real run with NO_COLOR=1 + timeout 1 0 pass-through 1 exited-1 YES 1/1

58 cells, 14 flips, 127 assertions, 0 fail. Every cell also ran a second time under the repo suite's own shell contract (bash -e -o pipefail -c) and agreed — 58 A/A assertions inside that 127, so the verdict does not depend on which invocation form is faithful.

F1–F3 are the point: on the bytes the lane actually produces, base and head are identical and the header grep executes zero times on both arms. F4 is the positive control for that census — the same vitest run with NO_COLOR=1 reaches leg 4 exactly once and there the PR flips a real uncaught TypeError from green to blocked.

Matcher evolution (what each stage bought)

The checkout is depth 2, so only the final matcher is reachable. The middle column is reconstructed from commit 1a84c6c4's own message text ([A-Za-z_$]*Error:) and compiled as a scratch release.yml; it is reported, not asserted. logs/evolution-run.log, witness in 01-ab-matrix… run output.

crash header beside the timeout v0 base v1 reconstructed v2 head
Error: write after end 1 1 1
TypeError: / RangeError: 0 1 1
Foo$Error: 0 1 1
LargeNonUtf8TextError: (digit) 0 0 1
DOMException: 0 0 1
AssertionError [ERR_ASSERTION]: 0 0 1
Error [ERR_MODULE_NOT_FOUND]: 0 0 1
PoolTimeout: / bare boom / vitest.TypeError: / Error [CODE]: / Error [ERR-X]: 0 0 0

1 of 13 → 4 of 13 → 8 of 13. This independently corroborates commit 2's claim that the first widening "still enumerated header shapes and left corners open": four real header shapes stayed green through v1.

Mutation matrix and vacuity

Witness: 02-mutation-matrix-kills-and-survivor.png. Raw: logs/mutation-run.log, logs/mutant-M*.log, logs/mutation-results.json.

Each mutant is one literal edit to the head release.yml inside a scratch worktree; the committed suite is run against it with -t 'names which failure this is, and never changes the exit code'. Every mutation is verified to have applied (occurrence count = 1) — a silent no-op reads exactly like a survivor.

mutant verdict killed by / evidence
M0 no mutation (control) GREEN Tests 1 passed | 47 skipped (48) — the command does collect the mutated file's test
M1 (Error|Exception)(Error) RED transport timeout beside an Exception-class header: expected +0 to be 1
M2 [A-Za-z0-9_$][A-Za-z_$] RED transport timeout beside a digit-bearing class header: expected +0 to be 1
M3 [A-Za-z0-9_$][A-Za-z0-9_] RED transport timeout beside a $-bearing class header: expected +0 to be 1
M4 delete ( \[[A-Za-z0-9_]+\])? RED transport timeout beside a coded exception header: expected +0 to be 1
M5 revert whole leg to ^[[:space:]]*Error: RED transport timeout beside a non-Error exception header: expected +0 to be 1
M6 delete | grep -qv 'Timeout calling' RED transport timeout, run completed: expected 1 to be +0
M7 ^[[:space:]]*^ GREEN — survivor Tests 1 passed | 47 skipped (48)
M8 pass-through exit 0exit 3 RED transport timeout, run completed: expected 3 to be +0

10 assertions, 0 fail (one per mutant's predicted outcome, plus the worktree-restore check). M6 and M8 are the positive controls, landed in the same file as the survivors and the same it() block, so "the suite does not cover this" and "my harness never ran your suite" are distinguishable.

Every kill quotes the behavioural mismatch — the step exited where the row demanded otherwise — not an import or compile break, so the reverted run fails the intended assertion.

M5 aborts at the first red row (the 12 rows live in one it()), so "all six new rows go red" is established by composing two measurements rather than one run: the suite's first red row (above), and calibrate.mjs §3, which drives each of the six committed rows through the base script individually and finds all six load-bearing — base gives 0 pass-through where each row demands 1.

Calibration against the repo's own instrument

logs/calibration-results.json, 30 assertions, 0 fail. The row table is extracted verbatim from both arms' copies of scripts/tests/release-workflow.test.js (base file 6 rows, head file 12 rows, 6 added — matching the diff) and replayed through the independently-extracted step script:

  • head arm × all 12 head rows → every row's own expectation holds.
  • base arm × the 6 rows that exist on main → reproduced exactly. This is the calibration cell: my extraction, shell contract and stub mechanism agree with the suite that gates main.
  • head arm × the 6 pre-existing rows → no regression.
  • Fixture bytes are the committed strings plus the trailing newline the committed sh stub's echo appends, so the replay is byte-identical to the repo's mechanism.

Corrections to the description

These are corrections to what the text says, not requests to change the code.

  1. "The defect is live on main." Not as the lane is configured. The pass-through branch requires all four && legs, and leg 2 (^[[:space:]]*Tests[[:space:]]+[0-9]+ passed) cannot match the colourised log the lane produces, so the branch is never entered and a crash cannot ship green today — on base or head (cells F1–F3: both arms exit 1, leg 4 executed 0 times). The matcher hole is real and reproduces the moment the log is plain (cell F4: base 0 pass-through → head 1 exited-1, on a real vitest uncaught-exception log). So the defect is masked, not live — and the masking also defeats the relief fix(release): report a workspace test run that fails with nothing failing #10805 was merged for.
  2. Commit 3's mutation table lists three constituents; the matcher has four. ( \[[A-Za-z0-9_]+\])? is pinned too — M4 kills it via the coded-header row. The commit title "pin each constituent" is correct; the table under-reports.
  3. The LargeNonUtf8TextError justification is correct, and worth stating because the obvious probe misleads. packages/core/src/utils/read-text-range.ts:134 sets this.name = 'LargeNonUtf8TextError', and Node then prints LargeNonUtf8TextError: range too large at column 0 (measured). A synthetic class LargeNonUtf8TextError extends Error {} without setting name prints Error: instead, because Error.prototype.name is inherited — so a quick reproduction appears to contradict the PR and does not.

Findings

F1 — Suggestion (premise): the guarded branch is unreachable in the release lane, so the widened matcher never executes there

Reproduce (logs/colour-run.log, 31 assertions, 0 fail):

CI=true npm run test:ci -w packages/core -- src/utils/read-text-range.test.ts --coverage.enabled=false > /tmp/lane.log 2>&1
grep -cP '\x1b' /tmp/lane.log                                              # 9  -> the log is colourised
grep -qE '^[[:space:]]*Tests[[:space:]]+[0-9]+ passed' /tmp/lane.log; echo $?   # 1  -> leg 2 NO_MATCH

The chain, each link measured:

  • CI=true is present in this Actions container (env | grep '^CI='), which is a sample of the lane's own runtime.
  • The shipped colour decision, node_modules/tinyrainbow/dist/chunk-BVHSVHOK.js:59: !("NO_COLOR" in i || …) && ("FORCE_COLOR" in i || … || "CI" in i). Colour turns on from the mere presence of CI and off only from the presence of NO_COLOR. FORCE_COLOR=0 therefore does not help — measured: 23 ESC lines with it set.
  • release.yml mentions neither variable (0 occurrences); ci.yml sets NO_COLOR: true in three "Run tests and generate reports" steps. The sibling lane that greps its own log already does this.
  • Real piped vitest runs: CI=true → 23 ESC lines, leg 2 NO_MATCH, leg 4 sees 0 headers. NO_COLOR=1 → 0 ESC lines, leg 2 MATCH, leg 4 sees the header. CI genuinely absent → plain. Real packages/core workspace log: same split.
  • With legs &&-chained, leg 2's failure short-circuits: the grep-shim census counts 0 executions of the header grep on all three real colourised fixtures, 1 on the same run with NO_COLOR=1.

Blast radius — every anchored leg, not just the one this PR touches. On a genuinely failing colourised run: leg 1 ^[[:space:]]*FAIL NO_MATCH (real line is \x1b[41m\x1b[1m FAIL \x1b[22m\x1b[49m c.test.js > …), leg 3 failed-tally NO_MATCH, leg 4 sees 0 headers. So an ordinary test failure in the release lane takes the else branch and is annotated ::error … with no failing test::No FAIL line and no transport timeout in the log. — a wrong diagnosis on the commonest failure there is. The unanchored grep -q 'Timeout calling' is colour-proof (measured), which is why the timeout branch is still entered and still emits the misleading "no passing tally to back it" text over a log that does contain a passing tally.

Bound — what this is not. Exit codes are untouched in every cell: the step always re-raises npm's status. This is fail-closed. No green release ships because of it, and no exploit is demonstrated. What is lost is (a) the relief #10805 bought, (b) the accuracy of the annotation that exists precisely to say which failure this was, and (c) the reachability of this PR's fix. What I could not measure: whether the self-hosted ecs-qwen-hk4-host runner that QwenLM/qwen-code routes to exports NO_COLOR in its environment. On GitHub-hosted ubuntu-latest — the other branch of the same runs-on expression, and every fork — colour is on.

Suggested fix (measured, one line, preserves the intent of both PRs)

Add NO_COLOR: true to the workspace_tests step's env, matching ci.yml. Measured consequences, not argued ones:

  • Hostile fixtures go clean: with NO_COLOR=1 the real vitest log has 0 ESC lines, leg 2 MATCHes, leg 4 executes once, and the PR's widening then flips a real uncaught TypeError from 0 pass-through to 1 exited-1 (cell F4).
  • Benign fixtures come out identical: the legitimate pass-through still passes through (A1, E1 both 0 pass-through at head), and the 6 pre-existing committed rows still hold at head (calibration §4).
  • The affected suite's counts are unchanged: release-workflow.test.js stubs npm, so a workflow env key cannot alter its outcomes — 449 passed in the targeted gate on both arms either way.

The two halves compose: NO_COLOR makes the branch reachable, and this PR makes it correct once reachable. Neither alone delivers the intent.

F2 — Suggestion: the leg's own filter swallows the unhandled error vitest embeds in the timeout line

node_modules/vitest/dist/chunks/rpc.-pEldfrD.js:48-53:

onTimeoutError(functionName, args) {
  let message = `[vitest-worker]: Timeout calling "${functionName}"`;
  
  if (functionName === "onUnhandledError") message += ` with "${args[0]?.message || args[0]}"`;
  throw new Error(message);
}

When the worker RPC times out while reporting an unhandled error, that error's message is interpolated into the one line the guard deliberately discards via grep -qv 'Timeout calling' — and only .message is interpolated, so the class name is lost and no header survives anywhere.

Reproduce (cell D2, logs/ab-run.log):

Error: [vitest-worker]: Timeout calling "onUnhandledError" with "Cannot read properties of null (reading 'port')"
      Tests  10614 passed (10614)

base → 0 pass-through, head → 0 pass-through. A real crash still ships green through the widened guard.

Not in the PR's accepted-tradeoff list, which names only a suffix-less producer class and a bare string throw. Ranked by observability this is worse than the shapes the PR did close: it fails silently, and the log line that would have named the cause has been consumed by the filter. Pre-existing, and today unreachable behind F1 — it becomes live the moment F1 is fixed, so the two should land together.

F3 — Suggestion: three unnamed siblings still clear the widened matcher

The PR is explicit that it narrows rather than closes the class, and both holes it names are confirmed (C1 PoolTimeout:0; C2 bare string throw → 0, with real bytes: node -e "throw 'boom'" prints boom and no header). These three are the same root cause and are not named:

shape head why
vitest.TypeError: ns (D1) 0 pass-through .[A-Za-z0-9_$]; real Node output — e.name = 'vitest.TypeError' prints verbatim
Error [ERR_ASSERTION]: 1 == 2 (D3) 0 pass-through the code group allows exactly one space
Error [ERR-SOMETHING]: boom (D4) 0 pass-through -[A-Za-z0-9_]+

A finding about the description's enumeration rather than about the change: an unnamed cost is worth naming even where it would have been accepted. D1 is the one with a plausible producer, since any library may set a namespaced error.name.

F4 — Nice to have: the matcher's indentation tolerance is unpinned (one surviving mutant)

M7 ('^[[:space:]]*[A-Za-z0-9_$]*…'^[A-Za-z0-9_$]*…) survives: Tests 1 passed | 47 skipped (48), suite green, hole reopened for any indented header.

Classification: coverage gap — not dead code and not redundant defence. Indented headers are real: cell B6 ( TypeError:) flips 0 → 1 on the widening, and Node renders a nested cause as cause: TypeError: inner. No committed row carries an indented exception header, although the pre-existing tally fixture does carry a leading space, so leg 2's identical [[:space:]]* is pinned. One row — the B6 fixture — closes it.

Not a merge condition; the guard is correct as shipped.

Non-finding: no ReDoS, no new lint findings

  • Scaling ladder (logs/evolution-run.log, 16 assertions): the widened regex over 2 k / 5 k / 20 k / 100 k characters of hostile authored text — identifier-class with no colon, identifier-class then Error with no colon, repeated Error, spaces then * — peaks at 31 ms, no rung near the 30 s cap. GNU grep compiles this ERE to a DFA (no back-references), so the curve is flat rather than superlinear.
  • shellcheck with the repo's own flags on both extracted step scripts: findings are byte-identical base vs head (5 pre-existing style notes; the SC2312 note sits on the changed line in both arms). Liveness proven — a planted cd /some/dir reports SC2164.
  • actionlint with the repo's flags on release.yml: exit 0, no findings. Liveness proven — a planted invalid if: expression is reported and exits nonzero.
  • bash -n on both extracted scripts: clean.

Targeted gates

gate head base attribution
Targeted 6-file scripts run, pristine trees, unloaded Test Files 2 failed | 4 passed (6), Tests 449 passed (449) identical release-workflow.test.js green at head. The 2 failures are collection-time and identical on both arms: install-script.test.js throws `zip`/`unzip` missing on a CI host (this container ships neither) and unit-vitest-configs.test.ts likewise. Environmental.
Full npm run test:scripts, head (root tree, loaded) 5 failed | 71 passed (76), 19 failed | 1989 passed Contaminated by this round — do not read as a PR effect. See the A/A control below.
Full npm run test:scripts, base tree (loaded) 5 failed | 71 passed (76), 4 failed | 1977 passed Failing names differ in both directions between the two runs, including tests that only differ by load (kills qwen subprocess descendants on timeout).
A/A control: check-tui-dep-direction.test.js alone root tree 15 failed | 52 passed (67); head tree 67 passed (67); base tree 67 passed (67) The 15 head-only failures were caused by this verification round: that suite scans the repo tree, and my scratch worktrees under tmp/ are two full copies of the repo source. Identical green in both pristine trees ⇒ not a PR effect.
yamllint not run not run pip3: Permission denied in this container; could not be installed.
typecheck / ESLint / workspace unit tests not run not run The diff contains no TypeScript and ESLint does not cover YAML. Listed under Not covered.

Not covered

  • No calibration against a real production artifact. This job has no GitHub token, so no release-run log, posted annotation, or step-summary output was retrievable, and this is a first round with no previous-report.md. The replay is therefore calibrated against the repo's own instrument instead (the 30-assertion calibration above), which is a second independent implementation of the same extraction — not a production artifact. What would have calibrated it properly: the workspace_tests log from release run 33713579913, named in the step's own comment.
  • Per-commit attribution is out of reach. Depth-2 shallow checkout: git rev-list HEAD^1..HEAD^2 returns 1 commit while the snapshot lists 3; git rev-parse --is-shallow-repository is true. The middle column of the evolution table is reconstructed from commit 1a84c6c4's message text and is reported, not asserted. I verified the aggregate HEAD^1..HEAD diff.
  • The base drifted between snapshot and checkout. The snapshot's baseRefOid 93e1597b… is not present locally; the merge ref was cut against c8595fc9…. Whether release.yml differs between those two bases could not be checked. The trial merge itself is clean — HEAD is GitHub's merge commit, and its tree's release.yml equals head's.
  • No real Vitest worker-RPC timeout was produced. This reproduces the shape the guard reads, not the stall that produces it: the Timeout calling "…" text is lifted from vitest's shipped RPC build, and in the colourised arms it is wrapped in the rendering measured from a real uncaught-exception header line. Nothing here demonstrates that a real transport timeout co-occurs with a passing tally.
  • The self-hosted ECS runner's environment (whether it exports NO_COLOR) is not measurable from this container; F1's reachability conclusion is stated for the GitHub-hosted path and flagged for the self-hosted one.
  • Not run: npm run typecheck, npm run lint (ESLint), prettier --check, every workspace unit suite, all integration suites, and any end-to-end release. node scripts/lint.js with no arguments was deliberately never invoked, since it runs prettier --write . over the working tree.
  • The Errors N error summary line vitest emits for an unhandled error is not read by any leg of the guard. Whether it would be a more robust signal than a header regex is a design question this round measured the need for but did not answer.
  • Exact assertion accounting. assertions.json (240/240) tallies only the five harnesses that ran to completion and emitted their own JSON: driver 127, calibrate 30, evolution 42, mutation-matrix 10, colour-regime 31. A sixth harness, harness/gates.mjs, was still executing its three vitest A/A runs when the round's budget expired, so none of its checks are counted. The gate facts in the tables above were measured and their raw output is on disk (logs/shellcheck-{base,head}.txt and their empty diff; logs/gate-actionlint.log exit 0; logs/gate-shellcheck.log; bash -n on both extracted scripts; the A/A control's 15 failed | 52 passed in the root tree against 67 passed (67) in each pristine tree; logs/gate-targeted-{head,base}.log). They are reported as measurements with their logs cited, not as counted assertions. logs/gates-run.log shows its eight static-gate and liveness assertions passing before it was cut off.
  • Nothing was posted to GitHub; no gh call was made. The two scratch worktrees were created under tmp/ and are removed at the end of the round.

Methodology

Environment: the CI verify job's container (node:22-bookworm family, node v22.23.2, GNU bash 5.2.15, GNU grep 3.8, 64 cores), working tree at the merge ref 4ea1d57f, npm ci and npm run build already complete. Scratch git worktrees for the base tip (HEAD^1) and the PR head (HEAD^2) live under tmp/pr10902-verify-20260904-165218/; the PR touches no dependency file, so both arms share the root node_modules as a clean control — and the suites under test import only yaml, glob and vitest, no @qwen-code/* workspace package, so the internal-symlink confound does not arise here (verified by resolving yaml and glob from inside the head worktree: both land in the root node_modules, which is unchanged by the diff).

Five harnesses drove the code, all under harness/ and re-runnable as node harness/<file>.mjs <artifact-dir>: ab-harness.mjs extracts the step's run: block with the yaml parser and executes it under GitHub Actions' documented Linux default shell with npm stubbed, a byte-exact fixture log, and a grep shim that records every grep the step invokes; driver.mjs runs the 29-fixture × 2-arm A/B twice per cell (both shell contracts); calibrate.mjs extracts the committed row table from both arms' test files and replays it; evolution.mjs compiles the reconstructed intermediate matcher and runs the scaling ladder; mutation-matrix.mjs applies nine single-point mutants and runs the committed suite against each; colour-regime.mjs runs real vitest — no stubs — in four colour regimes plus a real workspace run and a real failing run, then runs the guard's own four regexes over the resulting bytes.

Raw per-cell logs, per-mutant vitest output, real vitest captures (logs/real-*.log, logs/colour-*.log), the extracted step scripts with their sha256s, and every harness's JSON result live under logs/; the two evidence images are in evidence/. Assertion counts come only from those five harnesses' own tallies: 127 + 30 + 42 + 10 + 31 = 240 pass, 0 fail. fail counts unexpected outcomes only — every base-arm red predicted by the PR's claims is encoded as an expectation and scores as a pass.

Two harness defects were caught by their own assertions and fixed before the numbers above were taken, and both are recorded because each one would otherwise have become a false finding: the annotation oracle initially compared a bare title against a warning:-prefixed one (56 spurious failures), and the colour harness's "no CI" arm initially inherited this container's CI=true instead of removing it (3 spurious failures that would have misattributed the colour trigger).

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

01-ab-matrix-base-vs-head

02-mutation-matrix-kills-and-survivor

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

Qwen Code · sandboxed verification

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

The bot already has a review of its own on 4d4ce9e09a37805dc8455834c7ea0e254055fcf8, which still stands.

机器人在 4d4ce9e09a37805dc8455834c7ea0e254055fcf8 上已有自己的评审,且仍然有效。

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

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

@wenshao

wenshao commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Local verification — real release step, real Vitest, real production log

I rebuilt this locally against the real artefacts rather than re-reading the diff: the step body extracted verbatim from release.yml by YAML parse, run under the shell the runner actually reports, against logs that a real Vitest 3.2.7 really produced on Linux, plus the 957 KB ${log} reconstructed from the release run this guard was written for.

Verdict: merge it. Every claim in the description reproduces exactly, all five added probe rows are load-bearing under mutation, and there is no regression on any other row or platform. It is strictly better than main in the one direction that matters.

Separately — and not caused by this PR — the verification turned up two pre-existing defects that make the guard unreachable in production, one of which currently makes the whole workspace_tests gate non-blocking on the release lane. Details and a validated structural fix for the standing Critical are below.

Rig: PR head 4d4ce9e0, base origin/main 9bb2f853. Merge-base 93e1597b and origin/main produce a byte-identical step body (sha256:80060eee0708), so the main arm is the live one. Only ${{ matrix.shard }} was substituted; no ${{ survives. Run on ubuntu:24.04 (GNU grep 3.11, bash 5.2.21) and natively on macOS (BSD grep 2.6.0-FreeBSD) — identical results on both, so the widened class carries no cross-platform divergence.


1. The description's matrix reproduces, row for row

17 fixtures × both arms, same verbatim step, npm stubbed to print the log and exit 1 (137 for the signal row).

guard matrix

fixture main this PR
TypeError: beside the timeout exit 0"passed through" exit 1 defect → fixed
AssertionError [ERR_ASSERTION]: exit 0 exit 1 fixed
DOMException: / LargeNonUtf8TextError: / Foo$Error: exit 0 exit 1 fixed
tally, then a later AssertionError: exit 0 exit 1 whole-file/per-run gap closed
clean timeout + tally exit 0 exit 0 pass-through preserved
Error: write after end exit 1 exit 1 unchanged
no tally / failing tally / FAIL line / unexplained exit 1 exit 1 unchanged
signal death (137) 137 137 unchanged
PoolTimeout: / bare throw exit 0 exit 0 residual, as disclosed

scripts/tests/release-workflow.test.js on the PR tree: 47 passed | 1 skipped. The suite as it stands on main also passes against the PR's workflow, so nothing pre-existing was bent to fit. I traced the gate itself rather than assuming: PR run 33765303962 job 100681705369 shows > @qwen-code/qwen-code@0.22.3 test:scripts and ✓ scripts/tests/release-workflow.test.js (48 tests).

2. Every added probe row is load-bearing

Counterfactual mutation: each mutant is run twice — once against the suite as it stands on main, once against the suite this PR ships. Oracle is vitest's exit code.

mutation matrix

M1 (revert to bare Error:) and M2–M5 (delete the digits, the $, the Exception alternative, the Name [ERR_CODE]: group one at a time) are killed only by rows this PR addsmain's suite stays green on all five. M7/M8 (invert the exclusion, remove the leg) were already pinned. Two survivors, both minor and both pre-existing:

3. Pre-existing: the step does not run with pipefail, so the guard never executes

The runner prints the shell it uses. On the post-#10805 release run 33806806226, job 100824085040, the same step that carries this guard reports:

production shell

shell: /usr/bin/bash -e {0} — GitHub's default, without -o pipefail. The step's own comment says the opposite (`-o pipefail` is the step default, so `$?` is npm's). Every other pipeline-consuming step in release.yml sets set -uo pipefail in its own body; this one is the exception.

Consequence, measured on the verbatim step in ubuntu:24.04:

shell A/B

Under bash -e, npm … | tee "${log}" yields tee's status, so || { … } never runs and the step exits 0 — including for a shard with a genuinely failing test (FAIL fixture, npm exit 1 → step exit 0, no annotation). quality aggregates job results, so workspace_tests reports success and publish proceeds. Since #10805 merged no shard has actually failed, so nothing has shipped over red tests — but the release lane's workspace-test gate is inert right now. The repo's suite proves the guard under bash -e -o pipefail, which is not the shell the lane uses.

This is inherited from #10805, not introduced here. One line fixes it: add shell: bash to the step.

4. Pre-existing: the log the guard greps is ANSI-coloured, so three of the four legs are blind

Vitest colours its output when CI + GITHUB_ACTIONS are set, even piped into tee. Against the real ${log} (run 33713579913, job 100569275577, 957,393 bytes, 39,431 ESC bytes):

ansi blindness

The class name and its colon are not adjacent — ESC[31mESC[1mError ESC[22m: [vitest-worker]: Timeout calling … — and the tally is ESC[2m Tests ESC[22m ESC[1mESC[32m394 passed. So ^[[:space:]]*FAIL , ^[[:space:]]*Tests[[:space:]]+[0-9]+ passed and the failing-tally leg all return 0. I reproduced the same bytes from a fresh local Vitest run under the same env, so this is not an artefact of the log API.

Two things follow. The pass-through #10805 added cannot fire in production; and a shard with a real failing test is annotated exited N with no failing test, because leg 1 is blind too. Note this cuts for merging: the widened matcher is defence that only becomes reachable once the colouring is dealt with, and it cannot make anything worse in the meantime.

5. End to end on real Vitest bytes

No hand-written fixtures: a reporter that blocks onTaskUpdate past birpc's 60 s deadline produces a genuine Error: [vitest-worker]: Timeout calling "snapshotSaved". Every run below really timed out (71 s each), really exited 1 with dangerouslyIgnoreUnhandledErrors: false (the repo's Linux value), and really printed its own tally.

end to end

With colours on (block A) nothing reaches the pass-through on either arm. With NO_COLOR=1 (block B) the defect and the fix are both real on real bytes: main waves a genuine unhandled TypeError through as a pass, this PR blocks it, and the clean-timeout control stays green.

One more, on the log of the run this guard was written for: the pass-through does not fire there either, on either arm. Once de-coloured, that log carries three ordinary Error: lines that tests print as fixture data (Error: boom, Error: Unsupported mode "midnight"…, Error: Not implemented: navigation…), and any one of them defeats leg 4. So leg 4 is simultaneously too narrow for real crash headers and too broad for ordinary test output.

6. The standing Critical (R1-1) is real — and closable without enumerating class names

Confirmed, not synthetic. A real subclass whose name carries no Error/Exception suffix renders as PoolTimeout: worker pool exhausted and passes through on both arms. A census of this repository finds 26 of 293 distinct Error subclasses (9 %) with suffix-less class names, and four of them explicitly assign that suffix-less string to err.name — which is precisely the token Vitest prints as the header: GitPullFailure (packages/core/src/utils/git-branches.ts), ChannelLivenessFailure (packages/acp-bridge/src/channel-liveness.ts), ProbeRunFailure (packages/cli/src/commands/review/test-efficacy.ts), SubmitRefusal (packages/cli/src/commands/review/submit.ts).

One correction to the Risk & Scope section: a bare string throw does not print "no header at all". Vitest renders it as Unknown Error: a bare string, no class header. The matcher misses it only because of the space, so that corner is closer to closable than the description suggests.

There is also a cheaper third option than the two the description lists. Vitest already publishes the number the guard is trying to infer: the summary block prints Errors N error(s) whenever unhandled errors occurred. Compare that with how many of them were the transport:

structural rule

log (real Vitest / real production) Errors [vitest-worker]: Timeout calling rule this PR's regex
clean transport timeout 1 1 pass through pass through
unhandled TypeError 3 2 fail the shard fail the shard
unhandled PoolTimeout 3 2 fail the shard pass through
bare throw (Unknown Error:) 3 2 fail the shard pass through
production log, run 33713579913 4 4 pass through fail the shard

It agrees wherever the regex works, closes both rows where it does not, and gets the motivating run right. No class names, no enumeration, no shard re-run, no --reporter=json. It does need the log de-coloured first — which §4 requires anyway.

7. Recommendation

  1. Land this PR. It shrinks a live false-green on main and each constituent of the regex has a probe that goes red when removed. Holding it does not make main safer.
  2. Follow-up, P1, independent of this PR: add shell: bash to Run Workspace Tests. Until then the release lane's workspace-test gate does not block anything.
  3. Follow-up, same change: strip ANSI (or set NO_COLOR: '1' on the step) before the greps, then replace leg 4 with the count comparison above. That closes R1-1 structurally and retires the enumeration argument entirely.

Scope and limits

No release was run end to end; verification is against the verbatim step, real Vitest logs and the real production log. The genuine transport timeout was induced by a blocking reporter rather than by contention. The ECS self-hosted runner itself was not exercised — the shell finding comes from that runner's own job log. Findings 3–6 are pre-existing on main; none is a regression introduced by this PR.

中文说明

本地验证 —— 真实 release step、真实 Vitest、真实生产日志

我没有只看 diff,而是用真实构件在本地重建:用 YAML parse 从 release.yml 逐字提取 step 正文,在 runner 实际上报的 shell 下运行,喂给真实 Vitest 3.2.7 在 Linux 上真正产出的日志,外加从这条 guard 所针对的那次 release run 复原出来的 957 KB ${log}

结论:可以合入。 描述里的每一条断言都逐行复现,新增的 5 个探针行在变异测试下全部承重,其它行与另一平台都没有回归。在唯一重要的方向上,它严格优于 main

另外——并非本 PR 引入——验证过程中查出两个既有缺陷,它们让这条 guard 在生产中根本不可达,其中一个目前使 release 通道的 workspace_tests 门禁完全不拦截。细节以及针对未决 Critical 的一个已验证结构性修法见下。

装置:PR head 4d4ce9e0,base origin/main 9bb2f853。merge-base 93e1597borigin/main 产出的 step 正文逐字节相同sha256:80060eee0708),所以 main 臂就是线上那条。只替换了 ${{ matrix.shard }},替换后无 ${{ 残留。在 ubuntu:24.04(GNU grep 3.11、bash 5.2.21)与 macOS 原生(BSD grep 2.6.0-FreeBSD)各跑一遍,两边结果完全一致,说明放宽后的字符类没有跨平台分歧。

1. 描述里的矩阵逐行复现

17 个 fixture × 两臂,同一份逐字 step,npm 打桩为打印日志并退出 1(signal 行退 137)。

见图 3。mainTypeError:AssertionError [ERR_ASSERTION]:DOMException:LargeNonUtf8TextError:Foo$Error:、以及「先出 tally 再崩」六种形态全部 exit 0(假绿),本 PR 全部改为 exit 1;干净超时的 exit 0 通道保留;Error: write after end、无 tally、失败 tally、FAIL 行、无法解释、signal 137 六种行为不变;PoolTimeout: 与裸抛两行仍然 exit 0(作者已披露)。

PR 树上 scripts/tests/release-workflow.test.js47 passed | 1 skipped。把 main 上的那份测试文件拿来跑 PR 的 workflow 同样全绿,说明没有为了迁就改动而改旧断言。门禁本身我也核实了执行行而不是假设:PR run 33765303962 job 100681705369 里同时出现 > @qwen-code/qwen-code@0.22.3 test:scripts✓ scripts/tests/release-workflow.test.js (48 tests)

2. 新增探针行全部承重

反事实变异:每个变异体跑两次——一次用 main 上的测试文件,一次用本 PR 的测试文件,判据是 vitest 退出码(见图 4)。

M1(退回裸 Error:)与 M2–M5(分别删掉数字、$Exception 分支、Name [ERR_CODE]: 分组)只被本 PR 新增的行杀掉main 的测试文件对这五个全绿。M7/M8(反转排除、删掉整条腿)本来就被旧行钉住。两个存活者都很轻微且都是既有问题:

3. 既有缺陷:这个 step 根本不带 pipefail,guard 永远不执行

runner 会打印它用的 shell。在 #10805 合入之后的 release run 33806806226、job 100824085040 中,承载这条 guard 的同一个 step 上报:shell: /usr/bin/bash -e {0}(见图 1)——GitHub 的默认值,没有 -o pipefail。而这个 step 自己的注释恰恰写反了(`-o pipefail` 是 step 默认,所以 `$?` 是 npm 的)。release.yml 里其它所有消费管道状态的 step 都在正文里自己写了 set -uo pipefail,只有这一个没有。

ubuntu:24.04 里对逐字 step 实测(见图 2):bash -enpm … | tee "${log}" 取的是 tee 的状态,|| { … } 永远不执行,step 退 0 —— 包括真有测试失败的分片FAIL fixture、npm 退 1 → step 退 0、无任何 annotation)。quality 聚合的是 job 结果,于是 workspace_tests 报 success,publish 照常放行。#10805 合入至今没有分片真的失败过,所以没有真的带红发版,但目前 release 通道的 workspace 测试门禁是失效的。仓库的测试是在 bash -e -o pipefail 下证明这条 guard 的,而那不是该通道使用的 shell。

这是 #10805 带来的,不是本 PR 引入的。 一行即可修:给该 step 加 shell: bash

4. 既有缺陷:guard 所 grep 的日志带 ANSI 颜色,四条腿里三条是瞎的

设置了 CI + GITHUB_ACTIONS 后,即便管道给 tee,Vitest 依然上色。对真实 ${log}(run 33713579913、job 100569275577,957,393 字节、39,431 个 ESC 字节)实测(见图 5):类名与冒号并不相邻——ESC[31mESC[1mError ESC[22m: [vitest-worker]: Timeout calling …;tally 是 ESC[2m Tests ESC[22m ESC[1mESC[32m394 passed。于是 ^[[:space:]]*FAIL ^[[:space:]]*Tests[[:space:]]+[0-9]+ passed 和失败 tally 三条腿全部返回 0。我又在本地新跑一次 Vitest 复现出同样的字节,所以这不是日志 API 的产物。

两个后果:#10805 加的 pass-through 在生产中不可能触发;真有失败测试的分片会被标成 exited N with no failing test,因为第 1 条腿同样是瞎的。注意这一条其实支持合入:放宽后的匹配是一层防御,要等颜色问题解决后才可达,在此期间它不可能让任何事情变糟。

5. 在真实 Vitest 字节上的端到端

不用手写 fixture:一个在 onTaskUpdate 中阻塞超过 birpc 60 秒期限的 reporter,能产出真正的 Error: [vitest-worker]: Timeout calling "snapshotSaved"。下面每次运行都真的超时(各 71 秒)、在 dangerouslyIgnoreUnhandledErrors: false(仓库的 Linux 取值)下真的退出 1、并真的打印了自己的 tally(见图 6)。

带颜色时(A 段)两臂都到不了 pass-through。NO_COLOR=1 时(B 段)缺陷与修复在真实字节上都成立:main 把一个真实的未处理 TypeError 当作通过放行,本 PR 拦住它,干净超时的对照仍然绿。

还有一条:在这条 guard 所针对的那次运行的日志上,两臂的 pass-through 同样都不触发。 去色之后那份日志里有三条测试当作 fixture 打印的普通 Error: 行(Error: boomError: Unsupported mode "midnight"…Error: Not implemented: navigation…),任意一条都会打掉第 4 条腿。所以第 4 条腿对真实崩溃头太窄、对普通测试输出又太宽。

6. 未决 Critical(R1-1)属实 —— 而且不用枚举类名就能关掉

已确认,不是臆造。name 不带 Error/Exception 后缀的真实子类会渲染成 PoolTimeout: worker pool exhausted,两臂都放行。对本仓库做普查:293 个不同的 Error 子类中有 26 个(9%)类名不带后缀,其中 4 个把这个不带后缀的字符串显式赋给了 err.name——而 Vitest 打印的头正是这个 token:GitPullFailurepackages/core/src/utils/git-branches.ts)、ChannelLivenessFailurepackages/acp-bridge/src/channel-liveness.ts)、ProbeRunFailurepackages/cli/src/commands/review/test-efficacy.ts)、SubmitRefusalpackages/cli/src/commands/review/submit.ts)。

对 Risk & Scope 一处更正:裸字符串抛出并不是「完全没有头」。Vitest 会渲染成 Unknown Error: a bare string, no class header。匹配不到只是因为中间那个空格,所以这个角落比描述里说的更容易关掉。

另外还有一个比描述中两个选项都更省的第三条路。Vitest 其实已经把 guard 想要推断的那个数字直接印出来了:只要出现未处理错误,汇总块就会打印 Errors N error(s)。把它和其中有多少条是传输超时做比较(见图 7):干净超时 1 vs 1 → 放行;未处理 TypeError 3 vs 2 → 拦;未处理 PoolTimeout 3 vs 2 → (regex 会放行);裸抛 3 vs 2 → (regex 会放行);生产日志 run 33713579913 4 vs 4 → 放行(regex 会拦)。

它在 regex 有效的地方结论一致,在 regex 失效的两行上把口子堵上,并且对那次动机运行给出了正确判断。不枚举类名、不重跑分片、不需要 --reporter=json。前提是日志要先去色——而这正是第 4 节本来就要求的。

7. 建议

  1. 合入本 PR。 它缩小了 main 上一个真实存在的假绿,且正则的每个组成部分都有一个删掉就变红的探针。压着不合并不会让 main 更安全。
  2. 后续,P1,与本 PR 无关:Run Workspace Testsshell: bash。在此之前,release 通道的 workspace 测试门禁拦不住任何东西。
  3. 后续,同一处改动: 在 grep 之前去掉 ANSI(或给该 step 设 NO_COLOR: '1'),然后把第 4 条腿换成上面的计数比较。这会从结构上关闭 R1-1,也就不再需要争论枚举是否穷尽。

范围与限制

没有端到端跑过一次真实 release;验证针对的是逐字 step、真实 Vitest 日志和真实生产日志。真实的传输超时是用阻塞 reporter 诱发的,不是靠机器争用。ECS 自托管 runner 本身没有被驱动——shell 那条发现来自该 runner 自己的 job 日志。第 3–6 节的发现都是 main 上的既有问题,都不是本 PR 引入的回归。

wenshao
wenshao previously approved these changes Sep 4, 2026
@wenshao

wenshao commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@wenshao
wenshao enabled auto-merge September 4, 2026 17:41
@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

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: 438 passed · 0 failed · 438 total

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

中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)

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

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

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

Verification report

<!-- qwen-triage:verify -->
<!-- qwen-triage:verify-substantive -->

Sandboxed verification: ❌ not passed — findings reported (agent verdict) — follow-up round on an unchanged commit pair

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: 438 passed · 0 failed · 438 total

Flakiness gate: ✅ 1 changed test file × 5 identical rounds (48 passed (48) each), no divergence

中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)· 对未变化 commit 对的复核轮

本轮是复核轮HEADHEAD^1HEAD^2 三个 OID 与上一轮完全相同(merge 4ea1d57f、base c8595fc9、head 4d4ce9e0),作者未对上一轮的发现作出回应。按规则全部测量重新执行,未沿用旧报告的任何数字。

结论:findings(438 条脚本化断言,全部通过,0 失败)。

A/B 结论:在纯文本日志下(guard 分支可达的配置),放宽 matcher 确实生效——30 个 fixture × 2 臂 = 60 个 cell 中 14 个从 base 的 exit 0(放行)翻转为 head 的 exit 1(拦住),含 PR 自己列出的全部 6 行;合法放行、信号死亡(137)、FAIL 行、Error: write after end 均不变(见 01-ab-matrix-base-vs-head.png)。变异矩阵 13 个单点变异中 9 个被杀,且每个都由它对应的那一行测试杀掉;M0 对照为绿。

核心发现 F1(沿用且复测确认):release lane 实际产出的日志带 ANSI 颜色CI 存在即着色,release.yml 未设 NO_COLOR)。真实 vitest 字节下第 2 条判据 ^[[:space:]]*Tests[[:space:]]+[0-9]+ passed 不匹配 → 第 4 条(本 PR 放宽的那条)在 base 与 head 上都执行 0 次,两臂结果完全相同。加 NO_COLOR=1 后分支可达,此时真实 vitest 未处理错误(LargeNonUtf8TextError:)在 base 上 exit 0 放行、在 head 上 exit 1 拦住——即 PR 的修法正确,但它在当前 lane 配置下进不去(见 02-colour-census-lane-regime-vs-no-color.png)。本轮新增:packages/core/test-setup.ts 会在 worker 里 delete NO_COLOR,实测该删除不影响建议修法(worker 看到已删除、reporter 仍为纯文本)。

新增发现:F3 增补——Node 真实的嵌套 cause 行 [cause]: TypeError: inner[cause]: 前缀在两臂都匹配不到,因此"外层 header 无后缀、cause 是普通 TypeError"的崩溃在 head 上仍放行(cell D2c);已实测候选修法(容忍该前缀)可闭合,且 30 个 fixture 中 28 个结果逐字节不变、套件两侧皆绿——套件两侧皆绿正说明该轴无测试钉住。F4 新增——第 3 条判据(失败计数检查)没有任何一行提交测试钉住(变异存活),且删除后"一个全过摘要 + 一个失败计数摘要"的日志会放行变绿。

对上一轮报告的更正:其 B5 声称的"真实 node 字节" DOMException [AbortError]: 并非 Node 实际输出(实为 AbortError: operation aborted);其 F4 引用的 cause: TypeError: inner 是误抄,真实字节是 [cause]: TypeError: inner(两臂都不匹配)。

未覆盖:无真实生产产物校准(无 token);3 个 commit 中仅 1 个本地可达;未产生真实 worker-RPC 超时(复现的是日志形状 + 真实未处理错误,非停顿成因);yamllint 装不上(pip3: Permission denied);未跑 typecheck / ESLint / 各 workspace 单测;自托管 ECS runner 是否导出 NO_COLOR 无法在此测量。

Verification report

PR #10902 — deep verification (follow-up round)

Verdict: findings — 438 scripted assertions, 438 pass / 0 fail. Verified head 4d4ce9e09a37805dc8455834c7ea0e254055fcf8 (git rev-parse HEAD^2, equal to the snapshot's headRefOid); base tip c8595fc9d85ee348c03db5e1cda10a08b8898414 (HEAD^1); merge ref 4ea1d57fb1ab54444933920b64589ac297614a06.

This is a re-verification of an unchanged commit pair. HEAD, HEAD^1 and HEAD^2 are byte-identical in OID to the previous round's, so the author has not responded to it. Per the follow-up rule nothing was carried by diffing the old report: every measurement below was re-executed at this head with fresh harnesses. Where the previous round's claims are restated, they are restated because this round re-measured them, and two of them are corrected below.

Previous-finding status at this head

# finding (previous round) severity status now evidence this round
F1 the guarded branch is unreachable in the release lane as configured (colourised log defeats leg 2, so leg 4 never executes) Suggestion stands colour census re-run: 42–194 ESC bytes on lane bytes, leg 2 miss, leg-4 census 0/0 on both arms; NO_COLOR=1 → 0 ESC, leg 2 HIT, census 1/1, and a REAL vitest unhandled error goes base exit 0 → head exit 1. Suggested fix additionally proven to survive the test-setup.ts NO_COLOR-deletion objection (new probe).
F2 the leg's own grep -qv 'Timeout calling' filter swallows the unhandled error vitest embeds in the timeout line Suggestion stands cell D5: both arms exit 0 pass-through over Error: [vitest-worker]: Timeout calling "onUnhandledError" with "Cannot read properties of null (reading port)". Still absent from the PR's accepted-tradeoff list.
F3 three unnamed siblings still clear the widened matcher (vitest.TypeError:, two spaces before the code, hyphen in the code) Suggestion stands, and widened D1/D3/D4 reproduce; new sibling: Node's real nested-cause line [cause]: TypeError: inner matches neither arm (the [cause]: prefix blocks it), so a suffix-less crash whose cause is a plain TypeError still ships green at head (D2c). See F3 below.
F4 the matcher's indentation tolerance is unpinned (surviving mutant) Nice to have stands, with its justification corrected M7 survives again (48 passed (48)). But the previous round's cited producer ( cause: TypeError: inner) is a mis-transcription; the real bytes are [cause]: TypeError: inner, which matches neither arm. The tolerance's real producer is vitest's indented Unhandled Errors block (cell B9), not the cause rendering. See Corrections.
Corr. 1 "the defect is live on main" is inaccurate as the lane is configured correction stands re-measured: on lane bytes base and head are identical (exit 1, census 0/0); the hole is masked, not live.
Corr. 2 commit 3's mutation table under-reports: the ( \[CODE\])? group is pinned too correction stands M4 killed by the coded exception header row.
Corr. 3 LargeNonUtf8TextError really prints its own name (a naive class X extends Error {} repro misleads) correction stands packages/core/src/utils/read-text-range.ts:134 sets this.name; real Node bytes captured: LargeNonUtf8TextError: range too large at column 0.
non-finding no ReDoS in the widened matcher stands ladder re-run: 2 k/3 k/5 k/20 k over five hostile shapes, every rung 2–3 ms, none near the 30 s cap (logs/ladder-run.log).

Declined/deferred rows from the previous round: none were declined by the author; all of the above were simply not addressed, because the head did not move.

Central claim and scope

Central claim. Widening the exception-header grep in the workspace_tests transport-timeout pass-through — from ^[[:space:]]*Error: to ^[[:space:]]*[A-Za-z0-9_$]*(Error|Exception)( \[[A-Za-z0-9_]+\])?: — stops a crash that Node prints under a non-Error: class header from clearing all four legs and shipping a green release.

Secondary claim 1. The legitimate pass-through and the step's other branches are unchanged.
Secondary claim 2. The six added rows pin each constituent of the matcher, so a later narrowing goes red.

The two extracted step scripts differ on exactly one line (sha256 44accae8… base vs 6a6808fdd… head; diff = the matcher; logs/step-{base,head}.sh). Both arms run the extracted script under GitHub Actions' documented Linux default shell with npm stubbed and the log fed as bytes; the leg-4 execution census comes from a grep shim placed ahead of /usr/bin/grep, so "the changed leg never ran" is a count.

A/B table

Witness: 01-ab-matrix-base-vs-head.png. Raw: logs/ab-run.log, logs/ab-results.json. leg4 b/h is the shim census of how many times the changed grep executed per arm.

fixture what it carries (real bytes unless noted) base head flip leg4 b/h
A1 timeout + passing tally only (the legitimate pass-through) 0 pass-through 0 pass-through 1/1
A2 TypeError: Cannot read properties of null (reading 'port') 0 pass-through 1 exited-1 YES 1/1
A3 passing tally, then a LATER AssertionError [ERR_ASSERTION]: 0 pass-through 1 exited-1 YES 1/1
A4 Error: write after end (pre-existing committed row) 1 exited-1 1 exited-1 1/1
A5 signal death (stub 137) 137 137 0/0
A6 FAIL … line 1 1 0/0
B1 RangeError: 0 1 YES 1/1
B2 AssertionError [ERR_ASSERTION]: (coded, real) 0 1 YES 1/1
B3 what a DOMException really prints: AbortError: 0 1 YES 1/1
B4 LargeNonUtf8TextError: (digit-bearing, this repo's own class) 0 1 YES 1/1
B5 Foo$Error: (dollar-bearing) 0 1 YES 1/1
B6 Error [ERR_UNKNOWN_BUILTIN_MODULE]: 0 1 YES 1/1
B7 REAL write-after-end is coded: Error [ERR_STREAM_WRITE_AFTER_END]: 0 1 YES 1/1
B8 YAMLException: (js-yaml ships .name = "YAMLException") 0 1 YES 1/1
B9 indented TypeError: (vitest's Unhandled Errors block) 0 1 YES 1/1
C1 PR-admitted hole: PoolTimeout: 0 pass-through 0 pass-through 1/1
C2 PR-admitted hole: bare string throw (boom, no header) 0 pass-through 0 pass-through 1/1
D1 vitest.TypeError: (dotted name, real) 0 pass-through 0 pass-through 1/1
D2 [cause]: TypeError: inner in isolation (real Node rendering) 0 pass-through 0 pass-through 1/1
D2b the whole real nested-cause crash block (Error: outer + cause) 1 exited-1 1 exited-1 1/1
D2c PoolTimeout: outer whose CAUSE is a plain TypeError 0 pass-through 0 pass-through 1/1
D3 Error [ERR_ASSERTION]: (two spaces) 0 pass-through 0 pass-through 1/1
D4 Error [ERR-SOMETHING]: (hyphen in the code) 0 pass-through 0 pass-through 1/1
D5 the RPC timeout that swallowed the unhandled error (F2) 0 pass-through 0 pass-through 1/1
D6 CR-prefixed \rTypeError: 0 1 YES 1/1
D7 CRLF log 0 1 YES 1/1
E1 a passing test logging a header-shaped string mid-line 0 pass-through 0 pass-through 1/1
E2 a passing test logging a header-shaped string at line start 0 1 YES 1/1
E3 unrelated exit 7, no timeout 7 no-failing-test 7 no-failing-test 0/0
E4 failing tally, no passing tally 1 1 0/0

30 fixtures × 2 arms = 60 cells; 14 flips; 210 assertions, 0 fail. Every prediction is derived from the real grep -E verdicts of the four legs plus the step's documented control flow — never from a JS regex translation ([[:space:]] is a POSIX class JS does not implement; a JS translation returned false for a plain TypeError: line, which is how this round first mis-scored two cells).

The six PR test-plan rows behave exactly as the PR claims (A1–A6). B7 is new and worth noting: the pre-existing committed row pins Error: write after end, but real Node prints the coded form, which base's ^Error: never saw — the hole the PR closes was wider on real bytes than the committed row suggested.

Real bytes end-to-end (the money cell)

Witness: 02-colour-census-lane-regime-vs-no-color.png. Raw: logs/colour-run.log, logs/colour-results.json, logs/real-unhandled-*.log.

A real vitest run of a scratch test that lets an unhandled rejection escape, carrying this repo's own class name, piped exactly as the lane pipes it (2>&1 | tee):

regime ESC bytes leg 2 leg 4 census base head
CI set, no NO_COLOR (the lane as configured) 194 miss 0 / 0 1 exited-1 1 exited-1
CI + NO_COLOR=1 (the suggested fix) 0 HIT 1 / 1 0 pass-through 1 exited-1
CI + FORCE_COLOR=0 (the obvious workaround) 42 miss 0 / 0 1 1
CI genuinely absent (control) 0 HIT 1 / 1 0 0

On the plain bytes the log contains, verbatim, LargeNonUtf8TextError: Cannot read properties of null (reading 'port') at column 0 under vitest's Unhandled Rejection banner, plus Tests 1 passed (1) — and base passes it through as green while head blocks it. On the lane's own bytes neither arm even reaches the changed leg.

Mutation matrix

Witness: 03-mutation-matrix-kills-and-survivors.png. Raw: logs/mutation-run.log, logs/mutant-M*.log, logs/mutation-results.json, logs/survivor-run.log.

Each mutant is one literal edit to the head release.yml in a scratch worktree; the committed suite runs against it. Every mutation is verified applied (anchor occurs exactly once; post-mutation text carries the edit; written file sha matches) — a silent no-op reads exactly like a survivor.

mutant verdict killed by (the row that should pin it)
M0 no mutation (control) GREEN 48 passed (48) — the command does collect the file the mutants edit
M1 (Error|Exception)(Error) RED transport timeout beside an Exception-class header: expected +0 to be 1
M2 [A-Za-z0-9_$][A-Za-z_$] RED …digit-bearing class header: expected +0 to be 1
M3 [A-Za-z0-9_$][A-Za-z0-9_] RED …$-bearing class header: expected +0 to be 1
M4 delete ( \[[A-Za-z0-9_]+\])? RED …coded exception header: expected +0 to be 1
M5 revert whole leg to ^[[:space:]]*Error: RED …non-Error exception header: expected +0 to be 1
M6 delete | grep -qv 'Timeout calling' RED transport timeout, run completed: expected 1 to be +0 (positive control)
M7 ^[[:space:]]*^ in leg 4 GREEN — survivor 48 passed (48)
M8 pass-through exit 0exit 3 RED transport timeout, run completed: expected 3 to be +0 (positive control)
M9 leg 2 ^[[:space:]]*Tests^Tests RED transport timeout, run completed: expected 1 to be +0
M10 widen to (Error|Exception|Failure) GREEN — survivor 48 passed (48)
M11 delete leg 3 (the failing-tally check) GREEN — survivor 48 passed (48)
M12 [ "${status}" -lt 128 ]-lt 1024 RED transport timeout, killed by a signal: expected +0 to be 137
M13 candidate fix: tolerate a [cause]: prefix GREEN — survivor 48 passed (48)

9/13 killed; 81 assertions, 0 fail. Every kill quotes the behavioural mismatch (the step exited where the row demanded otherwise), not an import or compile break. M6/M8/M9/M12 are positive controls landed in the same file as the survivors, so "the suite does not cover this" and "my harness never ran your suite" are distinguishable.

Every survivor is adjudicated behaviourally, not by reading (logs/survivor-run.log, 58 assertions):

survivor head mutant what the difference is
M7 1 0 coverage gap: deleting the indentation tolerance reopens the hole (cell B9 is a real producer — vitest indents its Unhandled Errors block). Not dead code.
M10 0 1 coverage gap, benign direction: nothing pins the matcher's UPPER bound; a benign line starting Failure: would block the pass-through. Fail-closed.
M11 1 0 coverage gap, load-bearing: leg 3 is the ONLY thing blocking a log with a passing tally AND a failing tally and no FAIL line and no header; with it deleted that log ships green. No committed row carries a failing tally.
M13 0 1 candidate further fix: closes D2/D2c. Collateral check over all 30 A/B fixtures: 28 byte-identical outcomes, exactly D2 and D2c changed; committed suite green on both sides — which is itself the proof the suite pins nothing on this axis. The fixture that would pin it: a row carrying [cause]: TypeError: inner.

Calibration against the repo's own instrument

logs/calibration-run.log, 29 assertions, 0 fail. The row table is lifted verbatim out of both arms' copies of scripts/tests/release-workflow.test.js (base 6 rows, head 12 rows, +6, all six pre-existing rows surviving unedited and in order) and replayed through the independently-extracted step script using the repo's exact mechanism (#!/bin/sh\necho '${stub}'\nexit ${code} stub, bash -e -o pipefail -c):

  • base arm × the 6 rows that gate main → 6/6 reproduced. This is the calibration cell: my extraction, shell contract and stub agree with the suite that gates main.
  • head arm × all 12 head rows → 12/12.
  • head arm × the 6 pre-existing rows → 6/6 (no regression).

Corrections to earlier descriptions

These are corrections to what text says, not requests to change code.

  1. Correction to the previous report's cell B5. It cited DOMException [AbortError]: as "real node bytes". Node does not print that: throw new DOMException('operation aborted','AbortError') prints AbortError: operation aborted (the name argument), and new DOMException(msg) with no name has .name === 'Error' and prints Error: msg. The literal DOMException: header has no real Node producer; the Exception alternative's real producers among shipped dependencies are YAMLException (js-yaml, .name = "YAMLException"), XPathException (jsdom) and class HTTPException extends …. The row still pins the alternative correctly — the justification is what was wrong. Measured: logs/real-node-DOMException-AbortError.log, and a bounded census over node_modules (logs/real-node-headers.json companion greps).
  2. Correction to the previous report's F4 justification. It said "Node renders a nested cause as cause: TypeError: inner" and used that as the indentation tolerance's real producer. The real bytes are [cause]: TypeError: inner (logs/real-node-nested-cause-indented.log), which matches neither arm's matcher — the [cause]: prefix blocks it, so indentation is irrelevant there. The tolerance's real producer is vitest's indented Unhandled Errors block (cell B9), which does pin it behaviourally in the A/B even though no committed row does.
  3. Correction to the PR description: "The defect is live on main." Not as the lane is configured — carried from the previous round and re-measured here (see F1). The hole is real and reproduces the moment the log is plain; on the lane's own bytes it is masked on base and head.
  4. Refinement of the previous round's shellcheck numbers. On the verbatim run: block, shellcheck reports 9 findings including two error:-severity ones (SC2296, SC1083) — but those sit on the un-substituted ${{ matrix.shard }} expression, which Actions evaluates before bash ever sees the script. On the substituted bytes bash actually receives there are 5 findings, byte-identical base vs head (logs/shellcheck-{base,head}-substituted.txt), matching the previous round's count; the SC2312 note sits on the changed line in both arms. The previous round's "5 pre-existing style notes" was right about the substituted form; the mechanism is now stated precisely.
  5. Methodology correction that changes a gate's meaning. HEAD^1 (main tip) and HEAD^2 (PR head) differ by 407 files, +4195/−56142 — the branch is far behind main. A base-tree vs head-tree suite comparison therefore measures main's drift, not the PR (this round measured 1981 vs 1971 tests that way). The pure delta is HEAD^1..HEAD, exactly the two PR files, so the gate below compares base-tree against a third worktree at the merge commit.

Findings

F1 — Suggestion (premise, carried and re-measured): the guarded branch is unreachable in the release lane, so the widened matcher never executes there

Reproduce (logs/colour-run.log, logs/real-unhandled-CI.log):

CI=true npm run test:ci -w packages/core -- <a passing test> --coverage.enabled=false 2>&1 | tee lane.log
grep -c $'\x1b' lane.log                                                    # 6–31 ESC-bearing lines (42–194 ESC bytes) -> colourised
grep -qE '^[[:space:]]*Tests[[:space:]]+[0-9]+ passed' lane.log; echo $?    # 1 -> leg 2 NO_MATCH

The chain, each link measured in this container (a live sample of the lane's runtime): CI=true is present; node_modules/tinyrainbow/dist/chunk-BVHSVHOK.js:59 turns colour on from the mere presence of CI and off only from the presence of NO_COLOR; release.yml mentions neither (0 occurrences) while ci.yml sets NO_COLOR: true in three steps; FORCE_COLOR=0 does not help (presence, again — measured 42 ESC bytes with it set). With the legs &&-chained, leg 2's failure short-circuits: the shim census counts 0 executions of the changed grep on every lane-regime fixture, on both arms.

Blast radius — every anchored leg. On a genuinely failing colourised run, leg 1 (^[[:space:]]*FAIL ) and leg 3 also miss (the real lines are ^[[41m^[[1m FAIL ^[[22m^[[49m … and ^[[2m Test Files ^[[22m ^[[1m^[[31m1 failed), so the step takes the elif and annotates a plain test failure as Workspace tests exited 1 on a Vitest transport timeout — a wrong diagnosis on the commonest failure there is, while the exit code stays correct.

Bound — what this is not. Exit codes are untouched in every cell; the step always re-raises npm's status, so this is fail-closed and no green release ships because of it. What is lost is (a) the relief #10805 was merged for, (b) the accuracy of the annotation that exists to say which failure this was, and (c) the reachability of this PR's fix. Not measurable here: whether the self-hosted ecs-qwen-hk4-host runner exports NO_COLOR.

Suggested fix (measured, one line, preserves the intent of both PRs) — now also proven against the strongest objection in the code

Add NO_COLOR: true to the workspace_tests step's env, matching ci.yml. Measured consequences:

  • Hostile fixtures go clean: with NO_COLOR=1 the real vitest log has 0 ESC bytes, leg 2 MATCHes, leg 4 executes once, and the widening flips a real uncaught LargeNonUtf8TextError from 0 pass-through to 1 exited-1.
  • Benign fixtures come out identical: the legitimate pass-through still passes through (A1, and the pass scenario at head), and the 6 pre-existing committed rows still hold (calibration, head arm × base rows 6/6).
  • The affected suite's counts are unchanged: release-workflow.test.js stubs npm, so a workflow env key cannot alter it — 48 passed (48) in all 5 flakiness rounds.
  • New this round — the objection the code itself raises, measured: packages/core/test-setup.ts and packages/cli/test-setup.ts both delete process.env['NO_COLOR'] as a setupFiles entry, which looks like it would undo the fix from inside the test process. It does not: a probe test asserting process.env.NO_COLOR === undefined passes (the deletion really runs in the worker) while the reporter in the main process still emits 0 ESC bytes. The deletion is worker-scoped; the reporter's colour decision is made in the parent.

F2 — Suggestion (carried, stands): the leg's own filter swallows the unhandled error vitest embeds in the timeout line

node_modules/vitest/dist/chunks/rpc.-pEldfrD.js interpolates the unhandled error's .message into the Timeout calling "onUnhandledError" with "…" line — the one line the guard deliberately discards via grep -qv 'Timeout calling' — and only .message, so the class name is lost and no header survives anywhere. Cell D5: base 0 pass-through, head 0 pass-through. A real crash still ships green through the widened guard. Not in the PR's accepted-tradeoff list (which names only a suffix-less producer class and a bare string throw). Ranked by observability this is worse than the shapes the PR did close: it fails silently, and the line that would have named the cause has been consumed by the filter. Pre-existing; unreachable today behind F1; live the moment F1 is fixed — the two should land together.

ART=tmp/pr10902-verify-20260904-182249
node "$ART/harness/ab.mjs" "$ART" | grep -E '^D5 '     # base 0/1  head 0/1 -> pass-through on BOTH arms
grep -n 'Timeout calling' node_modules/vitest/dist/chunks/rpc.-pEldfrD.js   # line 49: the interpolation

F3 — Suggestion (carried, widened): the unnamed siblings, plus one the previous round mis-transcribed

The PR is explicit that it narrows rather than closes the class; both holes it names are confirmed (C1, C2, with real bytes). The carried siblings reproduce (D1, D3, D4). New this round: Node's real nested-cause rendering is [cause]: TypeError: inner, and the [cause]: prefix puts it outside both matchers — so a crash whose outer header is suffix-less (PoolTimeout:) but whose cause is a plain TypeError clears all four legs at head (D2c). The full real crash block with an Error:-suffixed outer header is caught (D2b), which bounds the shape honestly.

A measured candidate fix exists (M13: '^[[:space:]]*(\[[a-z]+\]:[[:space:]]*)?[A-Za-z0-9_$]*…): it closes D2 and D2c, leaves 28 of 30 A/B outcomes byte-identical, and leaves the committed suite green on both sides. That last fact is the unpinned-axis signal — the fix should ship with the fixture that would pin it (a row carrying [cause]: TypeError: inner).

ART=tmp/pr10902-verify-20260904-182249
node "$ART/harness/ab.mjs" "$ART" | grep -E '^(D2|D2c) '   # 0 pass-through on BOTH arms at head
node "$ART/harness/survivors.mjs" "$ART" | grep -E 'M13|CHANGED'  # head 0 -> M13 1; 28/30 identical, only D2 D2c changed
cat "$ART/logs/real-node-nested-cause-indented.log" | grep cause # '  [cause]: TypeError: inner' — the real bytes

F4 — Suggestion (new): leg 3 has no committed row, and it is load-bearing

M11 (deleting && ! grep -qE '^[[:space:]]*(Tests|Test Files)[[:space:]]+[0-9]+ failed' "${log}" \) survives: 48 passed (48). No committed row carries a failing tally. Behaviourally, leg 3 is the only thing blocking a log with a passing tally from one workspace and a failing tally from another and no FAIL line and no exception header — exactly the shape the --workspaces fan-out can produce — and with leg 3 deleted that log passes through green (logs/survivor-run.log). Bound: I could not produce a real vitest log with a failing tally and no FAIL line (a failing test prints FAIL ), so the consequence is demonstrated on a synthetic-but-possible shape; the coverage gap itself is proven. The PR added six rows to this it() and none of them touches leg 3.

ART=tmp/pr10902-verify-20260904-182249
node "$ART/harness/mutation.mjs" "$ART" | grep -E '^M11'   # green -> survivor
node "$ART/harness/survivors.mjs" "$ART" | grep -E '^M11'  # head 1 -> mutant 0: a failing tally ships green without leg 3
grep -c 'failed (' scripts/tests/release-workflow.test.js  # 0 -> no committed row carries a failing tally

F5 — Nice to have (carried, stands): the matcher's indentation tolerance is unpinned

M7 survives. Coverage gap, not dead code: cell B9 ( TypeError: indented, as vitest renders its Unhandled Errors block) flips 0 → 1 on the widening, so the tolerance has a real producer. One row closes it. Not a merge condition; the guard is correct as shipped.

ART=tmp/pr10902-verify-20260904-182249
node "$ART/harness/mutation.mjs" "$ART" | grep -E '^M7'    # green -> survivor
node "$ART/harness/survivors.mjs" "$ART" | grep -E '^M7'   # head 1 -> mutant 0: the hole reopens, no row notices
node "$ART/harness/ab.mjs" "$ART" | grep -E '^B9 '         # the real producer: indented header flips 0 -> 1

F6 — Nice to have (new): nothing pins the matcher's upper bound

M10 (widening to (Error|Exception|Failure)) survives. The suite can detect a narrowing but not a widening; a benign line starting Failure: would block the pass-through (cell E2's shape). Fail-closed, so benign — completeness reporting, not a merge condition. Related tradeoff worth naming in the PR text: the widening adds false-positive surface in the same direction (E2: a passing test that prints a header-shaped line at line start now blocks the pass-through; mid-line it does not, E1, because of the anchor).

ART=tmp/pr10902-verify-20260904-182249
node "$ART/harness/mutation.mjs" "$ART" | grep -E '^M10'   # green -> the upper bound is unpinned
node "$ART/harness/survivors.mjs" "$ART" | grep -E '^M10'  # head 0 -> mutant 1: a benign "Failure:" line over-blocks
node "$ART/harness/ab.mjs" "$ART" | grep -E '^(E1|E2) '    # mid-line safe (0/0), line-start blocks (0 -> 1)

Non-finding: no ReDoS, no new lint findings

  • Scaling ladder (logs/ladder-run.log, 20 assertions): five hostile shapes at 2 k/3 k/5 k/20 k characters, every rung 2–3 ms under timeout 30, none near the cap. GNU grep compiles this ERE to a DFA; the curve is flat. Input-writer boundary stated: the lane log is written by the repo's own suites, not by a fork contributor.
  • actionlint with the repo's own flags on release.yml: exit 0, 0 findings, on base-tree, head-tree and merge-tree. Liveness proven — a planted invalid if: expression is reported (unexpected end of input while parsing variable access) and exits 1.
  • shellcheck with the repo's flags on the extracted step scripts: 9 findings verbatim (including two SC2296/SC1083 that are ${{ }} extraction artifacts), 5 findings on the substituted bytes bash receives, byte-identical base vs head. Liveness proven — a planted unguarded cd reports SC2164.
  • bash -n on both extracted scripts: clean.

Targeted gates

gate base-tree (main tip) merge-tree (main tip + this PR) attribution
Full scripts/tests suite, pristine trees Test Files 2 failed | 74 passed (76), Tests 1981 passed (1981) identical The pure PR delta adds 0 tests and breaks 0. The 2 collection failures are environmental and identical on both arms: install-script.test.js needs zip (absent in this container — command -v zip empty) and a webui vite config needs vite-plugin-dts, unresolvable from a worktree; a docker build attempt inside one scripts test fails identically on both arms.
changed file at the merge ref ✓ scripts/tests/release-workflow.test.js (48 tests) 761ms green
flakiness: 5 identical rounds of the changed file 48 passed (48) × 5, exit 0 each no divergence
yamllint not run not run node scripts/lint.js --setup fails with pip3: Permission denied in this container; proven environmental by running the setup.
typecheck / ESLint / workspace unit suites not run not run The diff contains no TypeScript; ESLint does not cover YAML. Listed under Not covered.

A note on why the gate needed a third worktree: base-tree vs head-tree measured 1981 vs 1971 tests — a ten-test difference that is main's drift (407 files), not the PR. The merge-tree arm removes it.

Not covered

  • No calibration against a real production artifact. This job has no GitHub token, so no release-run log, posted annotation or step-summary output was retrievable; the previous round's suggestion to calibrate against release run 33713579913 remains unrun. The replay is calibrated against the repo's own instrument instead (29 assertions, above).
  • Per-commit attribution is out of reach. Depth-2 shallow checkout: git rev-list HEAD^1..HEAD^2 returns 1 while the snapshot lists 3; git rev-parse --is-shallow-repository is true. Unlike the previous round I did not reconstruct the intermediate matcher from commit 1a84c6c4's message text — the aggregate HEAD^1..HEAD diff is what is verified here, and the evolution table in the previous report stands as reported-not-asserted there.
  • No real Vitest worker-RPC timeout was produced. The Timeout calling "…" line is injected from vitest's shipped RPC build; the unhandled error beside it IS real (produced by real vitest). Nothing here demonstrates that a real transport timeout co-occurs with a passing tally — this reproduces the wire shape and the crash shape, not the stall that produces them.
  • The self-hosted ECS runner's environment (whether it exports NO_COLOR) is not measurable from this container; F1's reachability conclusion is stated for the GitHub-hosted path and for any runner where CI is present and NO_COLOR absent.
  • M11's failing-tally shape is synthetic; no real vitest producer demonstrated (see F4's bound).
  • Not run: npm run typecheck, npm run lint (ESLint), prettier --check, every workspace unit suite, all integration suites, and any end-to-end release. node scripts/lint.js with no arguments was deliberately never invoked, since it runs prettier --write . over the working tree.
  • The Errors 1 error summary line vitest emits for an unhandled error is still read by no leg of the guard (confirmed present in logs/real-unhandled-CI-NOCOLOR.log). Whether it would be a more robust signal than a header regex remains an open design question.
  • The DOMException: row's fixture is synthetic; no real producer prints that literal header (see Correction 1). The row's purpose (pinning the Exception alternative) is served, and real producers for that alternative exist in shipped dependencies.

Methodology

Environment: the CI verify job's container (node v22.23.2, GNU bash 5.2.15, GNU grep 3.8, 64 cores), working tree at the merge ref 4ea1d57f, npm ci and npm run build already complete. Scratch git worktrees for the base tip (HEAD^1), the PR head (HEAD^2) and the merge commit (HEAD) live under tmp/pr10902-verify-20260904-182249/; the PR touches no dependency file, so all arms share the root node_modules, and the suites under test import only yaml, glob and vitest (verified: yaml resolves from inside the head worktree to the root node_modules, which the diff does not touch), so the internal-symlink confound does not arise.

Seven harnesses drove the code, all under harness/ and re-runnable as node harness/<file>.mjs <artifact-dir> [repo]: extract.mjs pulls the step's run: block with the yaml parser; real-bytes.mjs captures what Node actually prints for twelve exception shapes; ab.mjs runs the extracted step on both arms over 30 byte-exact fixtures with a stubbed npm and a grep shim census; colour.mjs runs REAL vitest (no stubs) in four colour regimes and three scenarios and drives the resulting bytes through the real step script; mutation.mjs applies 14 single-point mutants and runs the committed suite; survivors.mjs re-extracts each mutated step and demonstrates every survivor's behaviour, plus the M13 collateral sweep; calibrate.mjs replays the repo's own committed row table; ladder.mjs times the matcher over hostile input; gates.mjs runs actionlint/shellcheck/bash -n with the repo's flags plus liveness proofs, the pure-PR suite gate and the flakiness rounds. Raw per-cell logs, per-mutant vitest output, real vitest captures (logs/real-*.log), the extracted step scripts with their sha256s and every harness's JSON live under logs/; the three evidence images are in evidence/. Assertion counts come only from those harnesses' own tallies: 210 + 21 + 81 + 58 + 29 + 20 + 19 = 438 pass, 0 fail. fail counts unexpected outcomes only — every base-arm red the PR predicts is encoded as an expectation and scores as a pass.

Six harness defects were caught by their own assertions and fixed before any number was reported, and are recorded because each would otherwise have become a false finding: a JS regex translation of [[:space:]] (returned false for a plain TypeError: line — the model was dropped in favour of real grep); a header-line heuristic that returned Node's node:assert:124 source banner instead of the header (two cells read as non-flips); a relative artifact dir that made RUNNER_TEMP resolve inside the worktree so tee failed and every cell collapsed to exit 1 (17 spurious failures); a bracket matcher desynchronised by an apostrophe inside a code comment (Vitest's); a noCI arm that inherited this container's CI=true; and a substring match that picked cua-driver-release-workflow.test.js when looking for the changed file.

Nothing was posted to GitHub; no gh call was made. The three scratch worktrees are removed at the end of the round.

Flakiness gate log

<pre><code>
rounds=5 files=1 skipped=0
file scripts/tests/release-workflow.test.js: (cd tmp/pr10902-verify-20260904-182249/merge-tree) 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 x 5 identical rounds (48 passed (48) each), no divergence
</code></pre>

Evidence images

01-ab-matrix-base-vs-head

02-colour-census-lane-regime-vs-no-color

03-mutation-matrix-kills-and-survivors

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

Qwen Code · sandboxed verification

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

01-ab-matrix-base-vs-head

02-colour-census-lane-regime-vs-no-color

03-mutation-matrix-kills-and-survivors

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

Qwen Code · sandboxed verification

…d re-run

The pass-through certified the run by enumerating exception-header shapes in
the log, but the header is producer-chosen: a crash under a class with no
Error/Exception suffix (PoolTimeout:) beside a passing tally cleared every
guard and shipped green (R1-1). No enumeration closes that class, so the log
is no longer allowed to grant the pass — the conditions only fast-fail on
clear breakage evidence, and the pass now requires re-executing the same
shard once and demanding a green exit. A real break fails the re-run; a
transient worker-RPC death passes it. The probe rows flip accordingly: the
suffix-less header now yields both verdicts depending on the re-run.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…r count

The pass-through certified a run by reading crash headers out of the log, and
no reading of headers can be complete: the class name is producer-chosen
(`PoolTimeout:`, a bare throw, a coded internal), which is the standing R1-1.
Re-running the shard would settle it, but the job's 45-minute budget is set by
contention and a shard that lost its transport has already spent up to 36 of
it, so a re-run does not fit in the one situation that produces these timeouts.

Vitest already publishes the number the guard was trying to infer: it prints
`Errors  N errors` whenever unhandled errors occurred. Summing that across the
log and comparing it with how many carried `[vitest-worker]: Timeout calling`
certifies the run without naming a single class. It closes the suffix-less and
bare-throw shapes no enumeration reaches, and it stops ordinary `Error:` lines
that a test merely printed from refusing the pass — the log this guard was
written for carries three of them.

Two pre-existing defects left the guard unreachable in production, and the
count is meaningless without them, so both are fixed here:

- The step ran under GitHub's default `bash -e {0}`, so `npm … | tee` yielded
  tee's status, the `||` handler never fired, and a shard with a genuinely
  failing test exited 0 (release run 33806806226, job 100824085040).
  `shell: bash` restores `-o pipefail`.
- The log is ANSI-coloured, which puts escapes between every label and its
  value, so three of the four conditions never matched real release bytes.
  `NO_COLOR` matches what ci.yml already sets on three steps.

18 probe rows drive the step extracted verbatim from the workflow; under
mutation each condition, the whole-file sum, both anchors and the plural in
the summary pattern dies to its own row.
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with @qwen-code /review. See workflow logs.

@yiliang114

Copy link
Copy Markdown
Collaborator Author

CI failure triage for head d07532e39 — none of the three red checks map to this PR's diff (.github/workflows/release.yml + scripts/tests/release-workflow.test.js).

1. Test (ubuntu-latest, Node 22.x)job 101240668886

  • Exactly one failed test in the whole log, in a file this PR does not touch:
    FAIL  components/MessageList.dom.test.tsx > MessageList — turn collapse (DOM) > drops the anchor instead of re-expanding when the user collapsed the anchored turn
    AssertionError: expected "spy" to be called 2 times, but got 1 times
    Test Files  1 failed | 255 passed (256)
         Tests  1 failed | 5731 passed (5732)
    
  • Each attempt burned ~31s while sibling DOM tests finished in 30–430ms: × MessageList — turn collapse (DOM) > drops the anchor … 31105ms (retry x2). The step's own disk/load sampler at that time shows shared-runner saturation: DFSAMPLE 05:08:26 tmpdir[/var/tmp/qwen-ci-yRUWTt] load[224.55 221.11 222.87] hosttests[144] (144 concurrent test processes on the host, load > 220).
  • The step then continued and every remaining shard passed (last one: Test Files 6 passed (6) at 05:08:28), followed by Terminated (SIGTERM) and ##[error]Process completed with exit code 1. at 05:08:29.
  • Attribution: saturated shared self-hosted runner — a timing-sensitive web-shell DOM test degraded to 31s/attempt and missed its waitForLoadCount window, plus a late SIGTERM on the step. Not related to this PR's files. Re-run expected to clear; if MessageList.dom.test.tsx fails again on a quiet runner, that test itself deserves a look (outside this PR).

2. web-shell E2E Smoke (ubuntu-latest, Node 22.x)job 101253238737

  • Failed in ~1s at Run transcript document browser gate (the browser smoke itself never started):
    No test files found, exiting with code 1
    filter: chat-transcript-document.test.ts
    
  • integration-tests/chat-transcript-document.test.ts exists on main (added 2026-09-04 in 7f7bce317 "feat: chat transcript mr2a html export (feat: chat transcript mr2a html export #10076)") but returns 404 at this PR's head — the branch predates that commit while the workflow gate already requires the file.
  • Attribution: stale branch vs main, unrelated to this PR's diff. A plain re-run will not fix this; merging main into the branch will.

3. review-prrun 33941797663

  • The job log is no longer downloadable (gh api .../jobs/101241328285/logs → HTTP 404 BlobNotFound), but the pipeline posted its own fallback comment on this PR: "Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted."
  • Attribution: review-lane infra failure — no review findings were produced against this diff. Retry via @qwen-code /review if a bot review is wanted.

No code changes needed from this PR for #1/#3; #2 clears once the branch syncs with main.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚠️ This run could not certify that any of this diff was reviewed. Suggestions are inline.

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

Not reviewed: the executable-script lint — qwen review script-lint produced no report.

Not reviewed: coverage — could not read the agents' transcripts (no subagent transcripts at /home/github-runner/actions-runner-hk1-16/_work/_temp/qwen-home/projects/-home-github-runner-actions-runner-hk1-16--work-qwen-code-qwen-code/subagents/820b1af7-ce99-4c54-a641-4a34bd86d348 (ENOENT: no such file or directory, scandir '/home/github-runner/actions-runner-hk1-16/_work/_temp/qwen-home/projects/-home-github-runner-actions-runner-hk1-16--work-qwen-code-qwen-code/subagents/820b1af7-ce99-4c54-a641-4a34bd86d348'). The harness writes one per agent; if there are none, either no agents ran or the harness could not write them.), so this run cannot show that any of the diff was read.

Not reviewed: verification — could not check that Step 4 and Step 5 ran (no subagent transcripts at /home/github-runner/actions-runner-hk1-16/_work/_temp/qwen-home/projects/-home-github-runner-actions-runner-hk1-16--work-qwen-code-qwen-code/subagents/820b1af7-ce99-4c54-a641-4a34bd86d348 (ENOENT: no such file or directory, scandir '/home/github-runner/actions-runner-hk1-16/_work/_temp/qwen-home/projects/-home-github-runner-actions-runner-hk1-16--work-qwen-code-qwen-code/subagents/820b1af7-ce99-4c54-a641-4a34bd86d348'). The harness writes one per agent; if there are none, either no agents ran or the harness could not write them.).

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

Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Brings in chat-transcript-document.test.ts so the web-shell E2E Smoke gate stops failing with 'No test files found'; branch was behind main.

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

  • R4-1 shell: 'bash' pinning gap — still stands — already reported (comment 3939637587)
  • R4-2 NO_COLOR: 'true' pinning gap — still stands — already reported (comment 3939637589)
  • R4-3 awk-anchor discriminating probe row — still stands — already reported (comment 3939637591)

Not explored to full depth (tool budget reached): "agent 1d": definitive confirmation that Git for Windows' bash environment on the windows-2022 lane provides (or lacks) awk — pacman-repo and build-extra archaeology establ…; "agent 6a": none — but one check I did not finish within budget: I did not verify the PR author's side claim about "26 of this repo's 293 Error subclasses" (motivation text…; "agent 6c": live Vitest 3.2.7 run resolving whether an onUnhandledError RPC-timeout wrapper is counted exactly once in the Errors summary (the uncertain link in finding….

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

Convergence: round 5 posted 4 inline comment(s), 3 of them reported for the first time; the previous round posted 4 (4 new). Findings keep coming back to the same files: .github/workflows/release.yml (findings in rounds 1, 4; 3 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.)

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

# least one transport line, so a log with no `Errors` summary at
# all counts 0 against it and the pass is refused, not granted.
errors=$(awk '/^[[:space:]]*Errors[[:space:]]+[0-9]+ errors?$/ { total += $2 } END { print total + 0 }' "${log}")
timeouts=$(grep -cE '\[vitest-worker\]: Timeout calling' "${log}" || true)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R1-1: [certifies-falsely] [new-surface] Re-asserted, corrected by this round's execution: the timeouts count greps the whole log unanchored, so any line containing the transport substring — a test's console output, or a code frame echoing source that contains it — can restore errors == timeouts parity over a real unhandled error and grant the pass-through. Two routes executed on real vitest 3.2.7 bytes: a passing test printing the shape beside one real transport death (timeouts=2 vs Errors 2 errors → exit 0 "Treated as a pass" while Vitest counted exactly 1 unhandled error), and a run whose only unhandled error is a real Error: write after end whose code frame echoes a source line containing the substring (exit 0; the byte-identical control without the substring exits 1). Correction to the round-4 comment: the claimed annotation mechanism (the auto-added github-actions reporter repeating the transport message) does NOT reproduce on the pinned vitest — that reporter skips errors with no module-graph frame, and probes show annotations=0, the pure-transport rescue works, and transport + real error is correctly refused; the round-4 suggested ^Error: anchor was also executed and does not close the test-print route (a column-0 printed line still matches it). Reachability is latent today — a repo-wide grep finds the substring only in scripts/tests/release-workflow.test.js, which does not run in the release lane — but the guard's own design premise is that test output is untrusted text, and the grant condition reads that text unanchored.

Witness:

probe — real vitest 3.2.7, verbatim-extracted HEAD guard (bash -e -o pipefail), npm stubbed:
P4 test-printed both shapes beside one real transport death:
   timeouts=2 errors=2 annotations=0 -> GUARD_EXIT=0 "...Treated as a pass." (Vitest counted 1)
P6 code-frame echo (only unhandled error: Error: write after end):
   matching line ' 10| // message: [vitest-worker]: Timeout calling "onUnhandledError" with...'
   -> GUARD_EXIT=0; flip (byte-identical run, substring absent from source) -> GUARD_EXIT=1
round-4 mechanism refuted:
   P1 pure transport -> annotations=0, GUARD_EXIT=0 (rescue works)
   P2 transport + real 'write after end' -> Errors 2 errors, annotations=0 -> GUARD_EXIT=1
round-4 suggested '^Error: ' anchor applied in scratch: P4 still exits 0

Key the count on provenance — count only transport messages Vitest itself printed inside its Unhandled Errors section (e.g. an awk state machine that enters on the section banner and counts header-shaped matches within it) rather than any whole-log line carrying the substring; note the ^Error: anchor alone was tested and is insufficient. Four release-lane workspaces set reporters (packages/core/vitest.config.ts:27 reporters: ['default', 'junit'], plus cli, web-shell and acp-bridge) and emit bare section lines with no annotation — the count must keep matching those. Add probe rows for both routes — a test-printed [vitest-worker]: Timeout calling line and the code-frame shape, each beside Error: write after end and Errors 2 errors, expecting stands — they must go red if the provenance keying is removed, and the pure-transport row must stay green.

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Holding for a maintainer design decision rather than another route-by-route patch. R1-1 is fix-induced and re-asserted across rounds 3→5: each fix closes one false-pass-through route and the next round finds another — here the unanchored timeouts grep over the whole log, inflated by Vitest's auto-added github-actions reporter. The robust fix is a design choice (anchor the count precisely / fail closed on any unhandled error regardless of parity / consume Vitest's structured output instead of grepping text), each with different release-safety tradeoffs. See the PR-level note. Leaving unresolved.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No change this round; leaving unresolved as held.

This thread already carries the author reply holding it for a maintainer design decision, and nothing in this round's commits touches the question it raises. What landed instead is orthogonal and test-side only: pins on shell: 'bash' and NO_COLOR: 'true', a discriminating row for the ^[[:space:]]*/$ anchors on the Errors sum, a row for the count-mismatch refusal, and that refusal's annotation now naming the fifth cause and printing both figures it compared. timeouts=$(grep -cE '\[vitest-worker\]: Timeout calling' …) is byte-identical to what this comment reviewed.

One thing worth recording against the options listed here: the ^Error: anchor is confirmed insufficient (your own execution, P4 still exits 0), and the provenance-keyed state machine you suggest is the same class of change as "consume Vitest's structured output" — a redesign of the guard's detection strategy, not a patch to it. That is the decision being held, and it should be made once rather than per route.


Held unresolved. This round pushed 73ff43f7 (refusal annotation names the fifth cause and prints both compared counts) and dc4e64ca (test-side pins only); neither changes the timeouts detection strategy this Critical is about, so it stays open for the maintainer decision.

&& ! grep -qE '^[[:space:]]*(Tests|Test Files)[[:space:]]+[0-9]+ failed' "${log}" \
&& ! grep -E '^[[:space:]]*Error:' "${log}" | grep -qv 'Timeout calling'; then
echo "::warning title=Workspace tests passed through a Vitest transport timeout::Every test passed and no other error was reported; Vitest's own worker RPC timed out. Treated as a pass."
&& [ "${errors}" -eq "${timeouts}" ]; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R5-1: [certifies-falsely] [new-surface] The pass-through fires over a workspace that died before printing any Vitest summary, because none of the four legs can see it. This round settled the disputed premise by execution: npm run test:ci --workspaces --if-present CONTINUES past a failing workspace, so one shard log accumulates every workspace's output. An earlier workspace's transport death contributes the passing tally + Errors 1 error + the transport line; a later workspace's pre-summary crash (a config-load failure, a runner-internal uncaught exception) contributes no Errors count, no FAIL line and no tally: status 1 < 128 ✓, passing tally ✓, no failing tally ✓, errors=1==timeouts=1 ✓ → exit 0 "Treated as a pass", and the release proceeds over a crash whose tests may never have run. The removed header-based leg rejected exactly this shape (the crash prints an Error: header); the count replacement cannot. The diff's rationale ("a passing tally cannot cover a later workspace's crash") argues only the sibling state where the later crash IS counted in Errors — the pre-summary sibling is unargued and false-passes. For completeness: the merge-base production shell (bash -e {0}, no pipefail) was dead code exiting 0 on every shape, and the old logic made executable refused this log — it never ran in production, but it did reject.

Witness:

npm run test:ci --workspaces --if-present (two-workspace scratch repo):
  ws-a failed exit 1 -> ws-b ran and printed WS-B-RAN; NPM_EXIT=1   (npm continues)
verbatim-extracted HEAD guard, driven through the real npm chain:
  ws-a: transport death + ' Tests  10614 passed (10614)' + ' Errors  1 error' (exit 1)
  ws-b: 'Error: Failed to load ./vitest.config.ts' (exit 1, no summary)
  -> GUARD_EXIT=0 "::warning ... Every test passed and all 1 unhandled error(s)
     Vitest counted were its own worker RPC timing out. Treated as a pass."
BASE-FORCED (old logic made executable): exit 1 "the failure stands"

Fail closed when a workspace produced no summary: npm prints a > <pkg> test:ci banner into the same tee'd log for every workspace it runs, so compare banners against summaries, e.g. ran=$(grep -cE '^> .* test:ci$' "${log}" || true) and summaries=$(grep -cE '^[[:space:]]*Test Files[[:space:]]+[0-9]+' "${log}" || true), then add && [ "${ran}" -eq "${summaries}" ] to the pass-through condition (verify the banner shape against a real run before landing). package.json:51"test:release:workspaces": "cross-env NODE_OPTIONS=\"--max-old-space-size=3072\" npm run test:ci --workspaces --if-present -- --coverage.enabled=false"; npm continues past failing workspaces (executed this round), so every workspace's summary lands in one log but a crashed workspace contributes none — a summary-count fix must expect one per workspace defining test:ci, not all workspaces. Add a row to scripts/tests/release-workflow.test.js: transport timeout + passing tally + Errors 1 error + a bare crash line (Error: Failed to load config) with no summary after it, stub exit 1, expecting stands — it must go green with the summary-coverage leg and red again if that leg is removed.

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Same non-converging class as R1-1: the guard cannot see a workspace that died before printing any Vitest summary, so the pass-through fires over a real pre-summary crash. Closing this route-by-route keeps spawning siblings (R1-1 recurred rounds 3→5). The converging fix is a design decision about the guard's whole detection strategy (anchor / fail-closed-on-any-unhandled-error / structured output), which needs maintainer judgment given the release-safety blast radius. See the PR-level note. Leaving unresolved.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No change this round; leaving unresolved as held.

This round's commits are test-side plus one annotation message, so the pass-through condition this comment is about — including the absence of any summary-coverage leg — is unchanged at the new head. The settled premise (npm run test:ci --workspaces --if-present continues past a failing workspace, so one shard log accumulates every workspace's output and a pre-summary crash contributes no Errors count, no FAIL line and no tally) is not disputed here, and it is precisely why this is held for a design decision rather than patched route-by-route.

Noting one interaction for whoever takes the decision: the summary-coverage leg you propose (ran banners vs summaries, && [ "${ran}" -eq "${summaries}" ]) would also close the new false-pass route that the R5-2 suggestion opens — a log entering the transport branch with no summary at all currently satisfies errors=0 == timeouts=0 vacuously. So the two threads want the same fix, and the banner shape needs verifying against a real run before either lands.


Held unresolved — no summary-coverage leg landed this round (73ff43f7 is annotation text only).

# retries re-run failing TESTS while an unhandled error fails
# the run outright. It has now cost this release three
# attempts (run 33713579913).
elif grep -q '\[vitest-worker\]: Timeout calling' "${log}"; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R5-2: The branch entry was narrowed to [vitest-worker]: only, but the pinned vitest also emits [vitest-pool]: (four sites in the pool RPC channel — main timing out on a wedged or killed worker, the OOM-under-contention shape this lane documents) and [vitest-api]: variants of the same transport-death class. A pool-side death now lands in the else branch whose annotation asserts "No FAIL line and no transport timeout in the log" while the log carries one — a factually wrong diagnosis that sends the oncall hunting a mystery crash — and the rescue the old pattern granted is lost. The exit status is re-raised correctly either way (errors>=1 vs timeouts=0 refuses the pass-through), so this is a diagnostics regression and a lost rescue attempt, not an unsound release.

Witness:

HEAD guard vs 'Error: [vitest-pool]: Timeout calling "executeTests"' + tally + ' Errors  1 error':
  GUARD_EXIT=1 "::error title=Workspace tests exited 1 with no failing test::No FAIL line and
  no transport timeout in the log..."   (annotation factually wrong)
BASE-FORCED (old entry grep -q 'Timeout calling'):
  GUARD_EXIT=0 "...Vitest's own worker RPC timed out. Treated as a pass."
shapes in pinned vitest 3.2.7 dist: [vitest-pool] chunks/coverage.DfSpMS-b.js:2602,2735,3063,3183;
[vitest-api] chunks/cli-api.DVe0nWUx.js:5180 (birpc 60s DEFAULT_TIMEOUT)
Suggested change
elif grep -q '\[vitest-worker\]: Timeout calling' "${log}"; then
elif grep -qE '\[vitest-(worker|pool|api)\]: Timeout calling' "${log}"; then

The count grep at line 616 must stay [vitest-worker]:-only — counting pool timeouts into timeouts would let errors==timeouts hold for a run whose worker never completed its assigned files, contradicting the guard's stated premise. Add a row carrying Error: [vitest-pool]: Timeout calling "executeTests" beside the tally and Errors 1 error, stub exit 1, expecting the transport-warning title — it is red today (the ::error unexplained title is emitted) and must go green with the widened entry, red again if reverted.

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not applying this one as written — the premise is confirmed but the suggested widening opens a new false-pass route. Measured, not inferred.

Premise checks out on the pinned vitest 3.2.7: grep -ro '\[vitest-\(pool\|api\|worker\)\]' node_modules/vitest/dist/[vitest-pool] 6 sites, [vitest-api] 1, [vitest-worker] 1. And your diagnosis of today's behaviour is right: a pool-side death lands in the else leg, whose annotation asserts "No FAIL line and no transport timeout in the log" while the log carries one.

But running the verbatim-extracted step with only the elif widened to \[vitest-(worker|pool|api)\]: Timeout calling, keeping the count grep [vitest-worker]-only exactly as your comment requires:

log = ' Tests  10614 passed (10614)'
    + 'Error: [vitest-pool]: Timeout calling "executeTests"'
    + 'Error: Failed to load ./vitest.config.ts'    # later workspace, died before any summary
HEAD     EXIT=1  ::error  title=Workspace tests exited 1 with no failing test
WIDENED  EXIT=0  ::warning title=Workspace tests passed through a Vitest transport timeout

Because timeouts never counts pool lines, a log that enters the branch on one and carries no Errors summary gets errors=0 and timeouts=0: the parity check passes vacuously, and with an earlier workspace's passing tally already in the same log, all four legs clear. The release proceeds over a workspace that never ran its tests. That is the R5-1 pre-summary shape, made reachable through a branch entry that today routes it to the ::error leg and re-raises the status — so the widening trades a wrong-words annotation and a lost rescue for a silently green release.

Widening the entry needs a precondition that the branch was earned by something countable — [ "${timeouts}" -gt 0 ] alongside the widened grep, or the summary-coverage leg from R5-1, which closes this case too. Either is part of the detection-strategy decision the two Criticals are held on, so leaving this unresolved rather than landing the one-liner. Happy to take whichever variant the maintainer picks.


Left unresolved on purpose: the measured false-pass above means this cannot be landed as a one-liner, and the precondition it needs is the same detection-strategy decision the two Criticals are held on.

Comment thread .github/workflows/release.yml Outdated
@yiliang114

Copy link
Copy Markdown
Collaborator Author

Holding this PR for a maintainer design decision — not pushing another autonomous patch this round.

Why: the two open Criticals (R1-1, R5-1) are a non-converging class of false-pass-through routes in the release-lane transport-timeout guard:

  • R1-1 is fix-induced and has been re-asserted across rounds 3→5: each fix closes one route (round 3 closed the header-enumeration class), and the next round finds another — here the unanchored timeouts grep over the whole log, which Vitest's auto-added github-actions reporter inflates (it is added whenever GITHUB_ACTIONS=true and a workspace leaves reporters empty, which most release-lane workspaces do).
  • R5-1 is a sibling route: the pass-through fires over a workspace that died before printing any Vitest summary, because none of the four guard legs can see a pre-summary crash (config-load failure, runner-internal uncaught exception).
  • The bot's own sandboxed verification did not pass (2026-09-04 18:07Z) and the review lane has been falling back (2026-09-05 03:47Z), so the automated lane isn't converging this either.

The underlying problem is a design choice about how to robustly distinguish a transport-timeout pass-through from a real failure when counting/grepping Vitest log text: anchor the count precisely, fail closed on any unhandled error regardless of errors == timeouts parity, or consume Vitest's structured output instead of grepping text. Each has different release-safety tradeoffs (false-red blocks a good release; false-pass ships a broken one). That is a maintainer call, not something to keep patching route-by-route.

The 5 open Suggestions (R4-4 pinning shell: bash / NO_COLOR, R4-5 discriminating probe row, R5-2 [vitest-pool:] / [vitest-api:] coverage, R5-3 annotation values) are all valid and should land together with the converged Critical fix — pushing them piecemeal now would just re-trigger a full-diff re-scan that re-mints R1-1/R5-1.

Leaving all threads unresolved pending that decision.

…used

The refusal annotation enumerated four causes, but the count comparison
adds a fifth that none of them described: `errors` and `timeouts` simply
disagreeing, with no crash in the log — the same transport message on two
lines is enough. A shard refused for that reason was told one of four
things happened when none did, and was not given the two numbers whose
mismatch was the whole story. The pass-side annotation already
interpolates `${errors}`, so name the fifth cause and print both figures.

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

Three things the guard depends on had no test opposing their removal:

- `shell: 'bash'` is the step's only source of `-o pipefail` (the workflow
  has no `defaults:` block), so it is what makes the guard live rather than
  dead code. The probe harness passes `-o pipefail` itself, so deleting the
  line as "redundant" left every row green while GitHub fell back to
  `bash -e {0}` and a failing shard exited 0 into the release.
- `NO_COLOR: 'true'` is load-bearing for every anchored pattern: Vitest
  colours from the mere presence of CI, and escapes between a label and its
  value match nothing. The stub npm prints plain-text fixtures, so nothing
  noticed.
- The `^[[:space:]]*` and `$` anchors on the `Errors` sum had no
  discriminating row: the loosened pattern sums identically on every
  existing fixture. A summary-shaped line a test printed at column 0 is what
  separates them, and it fails closed — the false-red this PR removes.

Also adds the row for the count-mismatch refusal, asserting the annotation
carries both figures. Each mutant was run: dropping `shell`, dropping
`NO_COLOR`, unanchoring the awk sum, and dropping the interpolation each
turn exactly one of these assertions red.

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Not explored to full depth (tool budget reached): "agent 1b": did not execute scripts/tests/release-workflow.test.js — the review worktree has no node_modules ; row outcomes were reasoned from the harness source, not ob…; "agent 8a": I did not induce a genuine [vitest-worker]: / [vitest-pool]: transport timeout end-to-end (birpc's timeout is not configurable from a test, so I reproduced th….

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

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

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

  • .github/workflows/release.yml:592 — [review] R5-2 still stands — the branch entry and the count were narrowed to [vitest-worker]: only, dropping vitest's sibling transport producers [vitest-pool]: and [vitest-api]: ; a pool-side death …
  • .github/workflows/release.yml:616 — [review] D6-1 the transport pattern is written twice in two regex dialects (BRE at :592, ERE at :616) and the whole count comparison rests on the two copies selecting the identical line set; a one-sided e…

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

# least one transport line, so a log with no `Errors` summary at
# all counts 0 against it and the pass is refused, not granted.
errors=$(awk '/^[[:space:]]*Errors[[:space:]]+[0-9]+ errors?$/ { total += $2 } END { print total + 0 }' "${log}")
timeouts=$(grep -cE '\[vitest-worker\]: Timeout calling' "${log}" || true)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R1-1: [certifies-falsely] [new-surface] Still standing from round 5 — this line is byte-identical to what that round reviewed. The pass-through grant derives timeouts from an unanchored whole-log grep, so log text carrying the transport substring can buy errors == timeouts parity over a real unhandled error and publish a green release behind an annotation asserting the opposite.

The asymmetry sits inside this diff's own stated design intent. The sibling figure one line above is deliberately anchored (^[[:space:]]*Errors…$) precisely so that words a test merely printed cannot move it, and the probe row transport timeout, and a summary-shaped line a test printed exists to pin that. This line has no equivalent protection, so the same class of printed text can move timeouts. The rationale in the comment above argues that header patterns are incomplete by construction and that the count needs no pattern, and it argues the no-Errors-summary case explicitly; it does not answer a grant bought by untrusted text.

Concretely: a shard log carrying a real unhandled Error: write after end plus one line of test console output echoing [vitest-worker]: Timeout calling "fabricated" at column 0, with Errors 2 errors, clears all four legs — status 1 < 128, passing tally present, no failing tally, errors=2 == timeouts=2 — so the step exits 0 and the release publishes over a real break.

Reachability is latent today rather than live, and that is worth stating plainly: an authoritative sweep finds no producer of that literal in the workspace_tests lane, so the trigger needs a future workspace test, a dependency's test output, or a captured-log fixture to print Vitest's internal worker string.

Witness:

probe at dc4e64ca — step lifted verbatim by `qwen review extract-step`, run under
`bash -e -o pipefail` with npm stubbed; every write in a scratch tree, the review
worktree left clean:

f1  (real `Error: write after end` + a fabricated transport line at column 0 + ` Errors 2 errors`)
  PR head:        STATUS=0  "::warning …Every test passed and all 2 unhandled error(s)
                    Vitest counted were its own worker RPC timing out. Treated as a pass."
  BASE (bash -e): STATUS=0, no annotation   <- the guard is dead code at the merge base
  BASE+pipefail:  STATUS=1                  <- the removed header grep REFUSED this log
f1b (fabricated transport line only + a real `TypeError:` + ` Errors 1 error`)
  PR head:        STATUS=0  "…all 1 unhandled error(s)… Treated as a pass."
anchored-header variant of the count: f1 STATUS=1, f1b STATUS=1; 13/15 matrix rows unchanged

reachability sweep — escaped pattern `\[vitest-worker\]: Timeout calling` over all 7857
tracked files plus the three vitest dist chunks that mention it:
  5 matching lines in exactly 3 files — release.yml (the guard),
  node_modules/vitest/dist/chunks/rpc.-pEldfrD.js (the producer), and
  scripts/tests/release-workflow.test.js — which is reached only by `npm run test:scripts`
  in the quality_scripts job, not by workspace_tests, whose command is
  `npm run test:ci --workspaces` (npm's --workspaces excludes the root package)

Anchoring the count on the transport's own header shape closes the demonstrated route. This is a regular code block rather than a one-click suggestion on purpose: the fix spans two lines, and applying it here alone is exactly the one-sided edit that opens a vacuous errors=0 == timeouts=0 grant.

elif grep -qE '^[[:space:]]*Error: \[vitest-worker\]: Timeout calling' "${log}"; then
...
timeouts=$(grep -cE '^[[:space:]]*Error: \[vitest-worker\]: Timeout calling' "${log}" || true)

Measured: that flips exactly the two fabricated rows and nothing else, and over a real vitest 3.2.7 log it counts 1 line where the unanchored message grep counts 3. It does not close R5-1 and it does not close the onUnhandledError payload route — those are separate holes. The full close remains provenance keying (count only transport messages Vitest itself printed inside its Unhandled Errors section, or take the decision from a small reporter's per-error structured output), which is the design decision this thread is held on.

The fix must keep counting the bare section lines emitted by the four workspaces that set reporters non-empty and therefore never get the auto-added github-actions reporter — packages/core/vitest.config.ts:27 reporters: ['default', 'junit'], plus packages/cli/vitest.config.ts:186, packages/web-shell/vitest.config.ts:26 and packages/acp-bridge/vitest.config.ts:44. Round 5 also established by execution that the ^Error: anchor alone does not close the test-print route when the printed line itself starts with Error: , so this is a narrowing rather than a close and should not be presented as one.

Please add two rows to the names which failure this is, and never changes the exit code table in scripts/tests/release-workflow.test.js — a real transport death beside a column-0 [vitest-worker]: Timeout calling "fabricated-by-test-output" line, a real Error: write after end and Errors 2 errors, expecting stands; and the same with a real TypeError: header in place of write after end — then remove the anchor from the count and confirm both rows go red, and that transport timeout, run completed, four transport deaths, four unhandled errors and two workspaces, both lost to the transport still pass through.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Still held for the maintainer design decision named in the PR-level note — no autonomous patch this round. The ^[[:space:]]*Error: \[vitest-worker\]: Timeout calling anchor is measured and would close the fabricated-line route, but this thread's own record says it is a narrowing, not a close: round 5 executed the ^Error: anchor and a test printing Error: [vitest-worker]: Timeout calling … at column 0 still bought errors == timeouts parity (P4 → exit 0). Landing it would leave this finding standing and re-mint it on the sibling route next round — the route-by-route loop the hold exists to stop. The pending decision (anchor precisely / fail closed on any unhandled error regardless of parity / consume Vitest's structured output) is unchanged, and your note that the full close is provenance keying is carried into it. Your two probe rows are the right witnesses and are recorded for whichever option lands. Thread left unresolved on purpose.

中文说明

仍然挂起,等待 PR 级说明中所述的维护者设计决策 —— 本轮不做自动化补丁。^[[:space:]]*Error: \[vitest-worker\]: Timeout calling 这个锚定经过实测,确实能关闭伪造行路线;但本讨论串自己的记录表明它只是收窄而非关闭:第 5 轮实际执行过 ^Error: 锚定,测试在第 0 列打印 Error: [vitest-worker]: Timeout calling … 时仍能买到 errors == timeouts 平价(P4 → exit 0)。落地它只会让本发现继续存在,并在下一轮沿姊妹路线被重申 —— 这正是挂起所要终止的逐路线循环。待决的选项不变(精确锚定计数 / 对任何未处理错误一律 fail closed 而不论计数是否相等 / 消费 Vitest 的结构化输出),你关于完整关闭应按出处键控的注记已纳入该决策。你给出的两个探针行是合适的见证,已为最终选定的方案记录在案。本讨论串刻意保持未解决。

&& ! grep -qE '^[[:space:]]*(Tests|Test Files)[[:space:]]+[0-9]+ failed' "${log}" \
&& ! grep -E '^[[:space:]]*Error:' "${log}" | grep -qv 'Timeout calling'; then
echo "::warning title=Workspace tests passed through a Vitest transport timeout::Every test passed and no other error was reported; Vitest's own worker RPC timed out. Treated as a pass."
&& [ "${errors}" -eq "${timeouts}" ]; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R5-1: [certifies-falsely] [new-surface] Still standing from round 5 — this condition, and the absence of any summary-coverage leg, are unchanged at this head. The pass-through fires over a workspace that died before printing any Vitest summary, because none of the four legs can see it: errors is summed only from Errors N errors lines, so a workspace that contributes no summary contributes 0 to that figure and nothing to any other leg.

npm run test:ci --workspaces --if-present continues past a failing workspace, so one shard log accumulates every workspace's output. An earlier workspace's transport death contributes the passing tally, Errors 1 error and the transport line; a later workspace's pre-summary crash — a config-load failure, a runner-internal uncaught exception — contributes no Errors count, no FAIL line and no tally. All four legs then clear and the step exits 0, so the release proceeds over a crash whose tests may never have run. Unlike R1-1 the trigger here is an ordinary event rather than adversarial content: a workspace whose vitest.config.ts fails to load is routine breakage.

Witness:

round 5's execution, unrefuted and re-confirmed at dc4e64ca:

npm run test:ci --workspaces --if-present (two-workspace scratch repo):
  ws-a failed exit 1 -> ws-b ran and printed WS-B-RAN; NPM_EXIT=1   (npm continues)

verbatim-extracted HEAD guard, driven through the real npm chain:
  ws-a: transport death + ' Tests  10614 passed (10614)' + ' Errors  1 error' (exit 1)
  ws-b: 'Error: Failed to load ./vitest.config.ts' (exit 1, no summary)
  -> GUARD_EXIT=0 "::warning ... Every test passed and all 1 unhandled error(s)
     Vitest counted were its own worker RPC timing out. Treated as a pass."
  BASE-FORCED (old logic made executable): exit 1 "the failure stands"

premise re-established this round from npm's own source: execWorkspaces wraps each
workspace in try/catch, sets process.exitCode, prints the lifecycle error and continues
the loop (npm/lib/commands/run-script.js:49-81), so every workspace's summary lands in
one log while a crashed workspace contributes none

Fail closed when a workspace produced no summary. npm prints a > <pkg> test:ci banner into the same tee'd log for every workspace it runs, so compare banners against summaries. Again a regular code block rather than a one-click suggestion: it adds two lines above and a fifth leg here.

ran=$(grep -cE '^> .* test:ci$' "${log}" || true)
summaries=$(grep -cE '^[[:space:]]*Test Files[[:space:]]+[0-9]+' "${log}" || true)
...
  && [ "${ran}" -eq "${summaries}" ]

Because package.json:51 runs npm run test:ci --workspaces --if-present, a workspace without a test:ci script prints no banner and produces no summary, so this must expect one summary per workspace that DEFINES the script rather than one per workspace, or it will refuse every run; and the existing rows two workspaces, both lost to the transport and four transport deaths, four unhandled errors must still pass through. Verify the banner shape against a real release run before landing it.

Please add a row to scripts/tests/release-workflow.test.js carrying a transport timeout, a passing tally, Errors 1 error and then a bare crash line (Error: Failed to load config) with no summary after it, expecting stands — it is green-by-omission today, and must go red once the leg lands and red again if that leg is removed.

Worth recording against the design decision this thread is held on: this same leg also closes the vacuous errors=0 == timeouts=0 grant that a one-sided widening of R5-2 opens, so the two threads want one fix rather than two.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Still held for the same maintainer design decision — no autonomous patch this round. The pre-summary-crash premise is undisputed (npm continues past a failing workspace, so one shard log accumulates every workspace's output), and the banner-vs-summary leg is the right shape; but the suggestion carries its own precondition — "verify the banner shape against a real release run before landing it" — which this environment cannot satisfy, and an unverified leg on a release gate is what this PR's history argues against. Your note that this leg and the R5-2 widening want one fix rather than two is recorded against the decision, as is the pre-summary-crash probe row. Thread left unresolved on purpose.

中文说明

同样挂起,等待同一个维护者设计决策 —— 本轮不做自动化补丁。pre-summary 崩溃这一前提没有争议(npm 会越过失败的 workspace 继续执行,因此一个分片日志会累积所有 workspace 的输出),banner 对 summary 的计数检查也是正确的形态;但该建议自带先决条件 —— "落地前先用真实 release 运行验证 banner 形态" —— 本环境无法满足这一点,而在 release 门禁上落地未经验证的检查正是本 PR 的历史所反对的。你关于这条检查与 R5-2 的放宽应当合并为一个修复而非两个的注记,以及 pre-summary 崩溃探针行,都已纳入该决策记录在案。本讨论串刻意保持未解决。

@wenshao

wenshao commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /takeover

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Sep 5, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@qwen-code-dev-bot

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

Copy link
Copy Markdown
Collaborator

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

中文说明

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Autofix round — no change

Outcome: no code change this round. The only standing feedback on the current head (dc4e64ca) is the two Critical findings that the PR author — a maintainer — has explicitly held for a maintainer design decision, and every other actionable item from earlier rounds has already landed or been dispositioned with evidence.

Triage of this round's feedback

Item Severity Disposition
R1-1 round-6 re-assertion: timeouts count greps the whole log unanchored (release.yml:616) Critical Escalated — held for maintainer design decision. Thread replied, left unresolved.
R5-1 round-6 re-assertion: no summary-coverage leg, so a pre-summary workspace crash passes through (release.yml:620) Critical Escalated — held for maintainer design decision. Thread replied, left unresolved.
R5-2 ([vitest-pool:]/[vitest-api:] branch entry) Suggestion No action — the round-6 review itself lists it under "Deferred under the convergence posture … not requested in this round"; the author additionally declined the one-line widening as-written with a measured false-pass (a pool-only line with no Errors summary grants a vacuous errors=0 == timeouts=0).
D6-1 (transport pattern written twice, BRE vs ERE) Suggestion No action — same reviewer-deferred section, not requested this round.
Prior-round findings (R4-4 shell: 'bash' / NO_COLOR pins, R4-5 awk-anchor probe row, R5-3 refusal-annotation counts) Suggestion Already landed in 73ff43f7 and dc4e64ca; nothing new raised against them.
Round-4 R1-1 duplicate (reporter-inflation mechanism) Critical Already declined with evidence in a prior round — round 5 refuted the mechanism by execution.
Sandboxed-verification suggestions (F1–F6) Advisory F1 (NO_COLOR / shell: bash) has since landed. F2/F3 are the same held detection-strategy class. F4–F6 are coverage nice-to-haves that the author's hold explicitly defers to the converged fix.

Why no patch on the two standing Criticals

  1. The decision is not this bot's to make. The author has stated it three times — inline on both threads and in a PR-level note: "That is a maintainer call, not something to keep patching route-by-route … Leaving all threads unresolved pending that decision." They then acted on it personally, landing the bounded Suggestions themselves while deliberately leaving the two Criticals open.
  2. The round-6 suggestions are narrowings by their own author's account, so they cannot close the findings they address. For R1-1, the review notes round 5's execution: a test printing Error: [vitest-worker]: Timeout calling … at column 0 still buys errors == timeouts parity under the ^Error: anchor — so landing the anchor re-mints R1-1 on the sibling route next round, the exact non-convergence the hold exists to stop. For R5-1, the suggested banner-vs-summary leg carries the review's own precondition — "verify the banner shape against a real release run before landing it" — which cannot be satisfied from this environment, and an unverified leg on a release gate is what this PR's history argues against.
  3. The pending design decision subsumes both findings. The author's note names the options — anchor the count precisely, fail closed on any unhandled error regardless of parity, or consume Vitest's structured output — and the round-6 review adds that R5-1's leg, R5-2's widening, and R1-1's full close (provenance keying) interact and want one fix rather than three. That call should be made once, by a maintainer.

Current state

  • scripts/tests/release-workflow.test.js at dc4e64ca: 48 passed (48) (run this round: npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/release-workflow.test.js).
  • The branch has merged origin/main (8a29855250); the earlier stale-branch smoke failure (chat-transcript-document.test.ts 404) is cleared — the file exists at this head.
  • No checks are currently failing on the head commit; the remaining red from the earlier triage was a runner-saturation flake and a review-lane infra fallback, neither attributable to this diff.

What unblocks this

A maintainer picks the detection strategy. Once picked, the fix lands with probe rows that fail on the pre-fix branch (per the verification gate) — the fabricated-transport-line rows for R1-1 and the pre-summary-crash row for R5-1 are already specified in the two open threads.

中文说明

Autofix 本轮 —— 无改动

结论:本轮不做代码改动。 当前 head(dc4e64ca)上唯一仍然悬而未决的反馈,是 PR 作者(维护者)已明确挂起、等待维护者设计决策的两个 Critical 发现;此前各轮中所有可执行的条目要么已经落地,要么已带着证据处置完毕。

本轮反馈的逐项分诊

条目 级别 处置
R1-1 第 6 轮重申:timeouts 计数对整份日志做无锚定 grep(release.yml:616) Critical 升级 —— 挂起,等待维护者设计决策。 已在该讨论串回复,保持未解决状态。
R5-1 第 6 轮重申:缺少 summary 覆盖检查,pre-summary 崩溃的 workspace 会被放行(release.yml:620) Critical 升级 —— 挂起,等待维护者设计决策。 已在该讨论串回复,保持未解决状态。
R5-2([vitest-pool:]/[vitest-api:] 分支入口) Suggestion 不处理 —— 第 6 轮评审自己把它列在"收敛姿态下暂缓……本轮不要求"一节;作者 additionally 还以实测的假放行(pool 行且无 Errors 汇总时会空泛地满足 errors=0 == timeouts=0)拒绝了单行放宽的写法。
D6-1(transport 模式写了两份,BRE 与 ERE) Suggestion 不处理 —— 同属评审方暂缓清单,本轮未要求。
此前各轮发现(R4-4 shell: 'bash' / NO_COLOR 钉住、R4-5 awk 锚点探针行、R5-3 拒绝注解打印计数值) Suggestion 已在 73ff43f7dc4e64ca 中落地;本轮没有针对它们的新意见。
第 4 轮 R1-1 重复项(reporter 膨胀机制) Critical 此前一轮已带证据拒绝 —— 第 5 轮通过执行驳倒了该机制。
沙箱验证的建议(F1–F6) 参考 F1(NO_COLOR / shell: bash)已经落地。F2/F3 属于同一个挂起中的检测策略问题。F4–F6 是覆盖层面的 nice-to-have,作者的挂起说明明确要求它们随收敛后的修复一起落地。

为什么两个悬而未决的 Critical 本轮不打补丁

  1. 这个决定不该由本机器人来做。 作者已三次表明 —— 两个讨论串内各一次、PR 级说明一次:"这是维护者的决定,不应该再逐条路线打补丁……在该决定作出之前,所有讨论串保持未解决。"随后作者亲自践行了这一立场:自己动手落地了有界的 Suggestion,而刻意把两个 Critical 留开。
  2. 按评审自己的说法,第 6 轮的建议只是收窄而非关闭,因此无法关闭它们所针对的发现。 对 R1-1,评审记录引用了第 5 轮的执行结果:测试在第 0 列打印 Error: [vitest-worker]: Timeout calling … 时,即使加上 ^Error: 锚定仍然能买到 errors == timeouts 的平价 —— 所以落地该锚定只会让 R1-1 下一轮在姊妹路线上被重申,这正是挂起所要终止的"打地鼠"循环。对 R5-1,建议的 banner 对 summary 计数检查带有评审自己写明的先决条件 —— "落地前先用真实 release 运行验证 banner 形态" —— 而本环境无法满足(无 token、无 release 通道),在 release 门禁上落地未经验证的检查正是本 PR 历史所反对的做法。
  3. 待决的设计决策涵盖两个发现。 作者的说明列出了选项 —— 精确锚定计数、对任何未处理错误一律 fail closed(不论计数是否相等)、或消费 Vitest 的结构化输出 —— 第 6 轮评审进一步指出:R5-1 的计数检查、R5-2 的放宽与 R1-1 的完整关闭(按出处键控)相互关联,应当合并为一个修复而非三个。这个取舍应当由维护者一次性作出。

当前状态

  • dc4e64ca 上的 scripts/tests/release-workflow.test.js:48 通过(48)(本轮实跑:npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/release-workflow.test.js)。
  • 分支已合并 origin/main(8a29855250);此前的分支过旧导致的 smoke 失败(chat-transcript-document.test.ts 404)已消除 —— 该文件在当前 head 上存在。
  • 当前 head 上没有失败的检查;早前分诊中剩余的红项是共享 runner 饱和导致的抖动和 review 通道的基础设施回退,均与本 diff 无关。

如何解除阻塞

由维护者选定检测策略。一旦选定,修复将带着在修复前分支上会变红的探针行落地(按验证门禁要求)—— R1-1 的伪造 transport 行用例和 R5-1 的 pre-summary 崩溃用例已在两个未关闭的讨论串中写明。

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


🧠 Handled by Qwen Code · model/模型 kimi-k3

@wenshao

wenshao commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Local verification, round 2 — the three follow-ups landed, checked on real bytes

Second local round, on head dc4e64ca. My first round (head 4d4ce9e0) said merge it, and named three follow-ups: shell: bash, NO_COLOR, and replacing the header matcher with Vitest's own error count. All three are now in this diff, so this round verifies them rather than repeating what already reproduced — plus the two Criticals the PR is held on.

Verdict: merge it. The motivating release run now passes through, for the first time since #10805 shipped the pass-through. Both standing Criticals were re-examined against real Vitest and real npm --workspaces, not against the diff:

  • R1-1 in its round-6 form does not gate. The route — the auto-added github-actions reporter inflating the unanchored timeouts grep — is measured unreachable, three independent ways below.
  • R5-1 is real, and I reproduced it end to end. It is not a regression: main passes the identical log through too. I also found a second route of the same shape (a tally that does not add up), likewise pre-existing. A validated fifth leg that closes both is at the end, with its A/B; it is a follow-up, not a merge condition.

Rig: head dc4e64ca, base origin/main 948872b1. Merge-base 87270610 and origin/main yield a byte-identical step body (sha256:80060eee0708) — unchanged since round 1, so the main arm is still the live one. Step bodies extracted verbatim from each release.yml by YAML parse; only ${{ matrix.shard }} substituted, no ${{ survives. Vitest 3.2.7, mawk 1.3.4 (Ubuntu's default awk), bash 5.2, CI=true GITHUB_ACTIONS=true throughout.


1. shell: bash — the gate goes from inert to blocking

shell

Round 1 measured this from the production job log; this round runs it. Under the shell main actually gets (/usr/bin/bash -e {0}, no pipefail), a shard with a genuinely failing test exits 0 and the release proceeds. With shell: 'bash' it exits 1. Cross-checked both ways round — same body under the other shell flips with it — so pipefail is the sole variable, not the guard.

This is the PR's one live behavioural change, and it is the right one: release.yml had no defaults: block and this is its only shell: key, so nothing else in the workflow moves.

2. NO_COLOR — measured on bytes Vitest really produced

nocolor

tinyrainbow enables colour on the mere presence of CI and disables it on the mere presence of NO_COLOR (key presence, any value — 'true' and true are equivalent, so this matches ci.yml's three steps). Confirmed on real output: coloured, the passing-tally leg matches 0 times; with NO_COLOR, 2.

On the real 988 KB production log the whole guard reads zero — FAIL 0, passTally 0, failTally 0, Errors 0 — with four transport lines present.

This de-risks rather than risks the lane. ci.yml's Run tests and generate reports steps — the ones that run these same workspace suites on every PR — already set NO_COLOR: true. The release lane was the outlier; after this PR the two lanes agree.

3. The motivating run, end to end

production

Release run 33713579913, job 100569275577 — the run this guard was written for. Fetched fresh, timestamps stripped to reconstruct the tee'd ${log} (987,795 B, 39,769 ESC bytes).

main this PR
as the lane logged it (coloured) blocked blocked
de-coloured (what NO_COLOR gives) blocked passed through

main refuses even de-coloured, because leg 4 matches the three ordinary Error: lines tests print as fixture data — Error: boom, Error: Unsupported mode "midnight"…, Error: Not implemented: navigation…. The count is unmoved by them: 4 summed errors, 4 transport lines. The description's central claim reproduces on the real artefact.

4. Errors == timeouts on genuine transport timeouts

transport

No hand-written fixtures: a reporter that blocks the main thread past birpc's 60 s deadline produces real Error: [vitest-worker]: Timeout calling "…" unhandled errors. Five configurations, each really timing out (~70 s), really exiting 1 under dangerouslyIgnoreUnhandledErrors: false.

The Errors N errors shape the awk pattern expects is exactly what Vitest emits ( Errors 1 error, Errors 3 errors), and the two counts agreed on every log where the guard reaches the count. Where they differed (4 files / 4 workers: 4 vs 5) a transport death had landed in a Failed Suites block instead of the unhandled-error list — leg 1 catches that first, and a failing tally backs it up.

R1-1's round-6 route is measured unreachable. Three independent checks:

  1. The github-actions reporter is auto-added only when a workspace leaves reporters empty — true for 20 of this repo's 27 vitest configs, so the premise holds. But its onFinished skips any error whose stack has no project-local frame (const stack = result?.nearest; if (!stack) continue;), and the transport error's stack is entirely node_modules + node internals. Measured with the reporter live: 0 extra transport lines.
  2. Control, same rig: a genuinely failing test does produce ::error file=… — so the reporter was running; the absence is the finding, not a dead rig.
  3. Census of the repo: no workspace source or test file contains the literal [vitest-worker]: Timeout calling. The only two files that do are release.yml itself and scripts/tests/release-workflow.test.js, which runs under test:scripts, not test:release:workspaces.

I also re-ran the census the description cites: 296 Error subclasses, 26 suffix-less (9 %), five of which assign the bare name to err.name (BenchmarkFailure, ChannelLivenessFailure, GitPullFailure, ProbeRunFailure, SubmitRefusal). The description's 293/4 is conservative; the argument holds either way.

5. Mutation — 11 of 14 die

mutation

Each mutant applied to release.yml, oracle is this PR's own 47-row suite. Killed: dropping the count comparison, making the sum keep only the last summary, unanchoring the branch entry, unanchoring the count grep, dropping the passing-tally / failing-tally / signal legs, narrowing the summary pattern to singular error, dropping its trailing $, and dropping either shell: bash or NO_COLOR.

Three survive, all width/anchor and all fail-closed in direction: ^[[:space:]]* on the summary sum (M9) and on the passing-tally grep (M13), and widening leg 2 to bare Tests (M14, #10805's leg). The description discloses two of these; M13 is the third.

One small mismatch worth a comment fix rather than code: the row commented "the summary sum is anchored on the section-line shape" (Errors 2 errors occurred in fixture data) is discriminated by the trailing $, not by the leading anchor — M12 dies to it, M9 does not.

6. What the count still cannot see — both reproduced, neither a regression

routes

A — R5-1, confirmed. Real npm run test:release:workspaces over two real workspaces: ws-a takes a genuine transport timeout and prints a complete green tally; ws-b dies at config load under a real suffix-less header (ChannelLivenessFailure: — one this repo actually assigns to err.name). npm runs the rest of the workspaces regardless, so the log ends green. Both main and this PR exit 0, annotated "Every test passed" — while a whole workspace never ran.

B — new, same shape. A transport timeout that costs a whole test file its results prints Test Files 1 passed (2) / Tests 1 passed (2). The discriminator is right there in the log — 1 reported out of 2 collected — and leg 2 does not read it. Both arms exit 0. Half the shard was lost.

Both are pre-existing: main behaves identically on real bytes, so neither is introduced here. What is new is that they are now reachable — before this PR the pass-through could not fire at all, and the step could not fail at all.

One honest caveat on the swap: the count is not a strict superset of the header rule. A pre-summary crash whose header happens to be exactly Error: is caught by main's leg 4 and passed through by the count (measured: main exit 1, this PR exit 0). That is a regression against main's intended rule, not against its behaviour — on main that path is unreachable in production for both of the reasons this PR fixes.

A validated fifth leg closes both. It reads only what npm and Vitest already print, no headers:

started=$(grep -cE '^> [^ ]+@[^ ]+ test:ci$' "${log}" || true)
reported=$(grep -cE '^[[:space:]]*Test Files[[:space:]]|No test files found' "${log}" || true)
incomplete=$(awk '
  /^[[:space:]]*Tests[[:space:]]+[0-9]/ {
    line = $0
    sub(/^[[:space:]]*Tests[[:space:]]+/, "", line)
    if (!match(line, /\([0-9]+\)[[:space:]]*$/)) { bad++; next }
    total = substr(line, RSTART + 1, RLENGTH - 2) + 0
    sub(/[[:space:]]*\([0-9]+\)[[:space:]]*$/, "", line)
    sum = 0
    n = split(line, seg, /\|/)
    for (i = 1; i <= n; i++) if (match(seg[i], /[0-9]+/)) sum += substr(seg[i], RSTART, RLENGTH) + 0
    if (sum != total) bad++
  }
  END { print bad + 0 }' "${log}")
#   … && [ "${started}" -eq 0 -o "${reported}" -ge "${started}" ] \
#   && [ "${incomplete}" -eq 0 ] \
log this PR + leg 5
production run 33713579913 (de-coloured) pass through pass through — preserved (started=23 reported=23 incomplete=0)
clean transport timeout, complete tally pass through pass through — preserved
workspace died before any summary (A) pass through refused
one test file's results lost (B) pass through refused

All 47 of this PR's own rows still pass with it applied, and passed + skipped == collected holds on every one of the 20 summaries in the production log, so it does not false-red on skips. It needs mawk-compatible awk only — already the case, and six other workflows in this repo use awk.

7. Recommendation

  1. Merge. It fixes a live non-blocking release gate, makes the guard reachable at all, and gets the motivating run right on the real log. Every added line is pinned by a test that goes red when removed.
  2. shell: bash is a live change — the workspace-test gate starts blocking releases. That is the point of the step, and it is what a maintainer should be signing off, not the regex.
  3. R1-1 as re-asserted in round 6 should not hold this PR: the route is measured unreachable, with a control proving the rig.
  4. R5-1 and the tally-completeness route are worth a follow-up, not a merge condition — they are equally present on main. Leg 5 above is validated and ready if you want it folded in instead.
  5. Two cosmetic: the annotation says "Every test passed" on a shard where a tally reported fewer tests than it collected; and the M9 comment attributes the fixture row to the wrong anchor.

Scope and limits

No release was run end to end. Verification is the step body extracted verbatim, real Vitest 3.2.7, real npm run --workspaces, and the real production log. Transport timeouts were induced by blocking the main thread past birpc's deadline rather than by host contention. The ECS self-hosted runner itself was not exercised. The de-coloured production log is the real bytes with ESC sequences removed, not a re-run under NO_COLOR; the §2 NO_COLOR measurements are from fresh local runs. Full test:scripts has four failures in files this PR does not touch — they reproduce identically on origin/main in this environment (root user, symlinked node_modules); release-workflow.test.js itself is 47 passed / 1 skipped on both, and CI is green on dc4e64ca.

中文说明

本地验证 · 第 2 轮 —— 三条后续项已落地,在真实字节上复核

第二轮本地验证,针对 head dc4e64ca。我的第一轮(head 4d4ce9e0)结论是「可以合入」,并点了三条后续项:shell: bashNO_COLOR、以及用 Vitest 自己的错误计数替换头部匹配。三条现在都在这份 diff 里,所以本轮只验证这三条以及本 PR 被压住的那两个 Critical,不再重复已经复现过的内容。

结论:可以合入。#10805 引入 pass-through 以来,那次动机运行第一次真正被放行。两个未决 Critical 我都是拿真实 Vitest 和真实 npm --workspaces 重新检验的,而不是读 diff:

  • R1-1 在第 6 轮的那个形态不构成阻塞。 所谓「自动追加的 github-actions reporter 会撑大未锚定的 timeouts grep」这条路径,经实测不可达,下面有三条独立证据。
  • R5-1 属实,我端到端复现了。但它不是回归main 对同一份日志同样放行。我还发现同一形态的第二条路径(tally 数目对不上),同样是既有问题。文末给出一条已验证的第五条腿可以同时关掉两者;那是后续项,不是合入条件。

装置:head dc4e64ca,base origin/main 948872b1。merge-base 87270610origin/main 产出的 step 正文逐字节相同sha256:80060eee0708),与第 1 轮一致,所以 main 臂仍是线上那条。两臂 step 正文都用 YAML parse 逐字提取,只替换 ${{ matrix.shard }},无 ${{ 残留。Vitest 3.2.7、mawk 1.3.4(Ubuntu 默认 awk)、bash 5.2,全程 CI=true GITHUB_ACTIONS=true

1. shell: bash —— 门禁从失效变为真正拦截(图 1)

第 1 轮是从生产 job 日志里读出来的,这一轮是跑出来的。在 main 实际拿到的 shell(/usr/bin/bash -e {0},无 pipefail)下,真有测试失败的分片退出 0,release 照常放行;加上 shell: 'bash' 后退出 1。两个方向都做了交叉对照——同一份正文换另一个 shell 结论就跟着翻——所以变量是 pipefail,不是 guard 本身。

这是本 PR 唯一一处线上行为变更,而且方向正确:release.yml 没有 defaults: 块,这也是它唯一一处 shell:,工作流其它部分不受影响。

2. NO_COLOR —— 在 Vitest 真正产出的字节上实测(图 2)

tinyrainbow 只要环境里有 CI 就上色,只要有 NO_COLOR 就关色(只看键是否存在,取值无关,所以 'true'true 等价,和 ci.yml 那三处一致)。在真实输出上确认:带色时通过 tally 那条腿匹配 0 次;NO_COLOR 下匹配 2 次。

在真实的 988 KB 生产日志上,整条 guard 读到的全是零——FAIL 0、passTally 0、failTally 0、Errors 0——而日志里明明有 4 条传输超时行。

这一改是在降风险而不是加风险。 ci.ymlRun tests and generate reports(每个 PR 上跑同一批 workspace 套件的那几步)本来就设了 NO_COLOR: true,release 通道才是那个例外;改完两条通道就一致了。

3. 动机运行的端到端复现(图 3)

release run 33713579913、job 100569275577 —— 这条 guard 的由来。重新拉取,剥掉时间戳前缀复原出 tee 的 ${log}(987,795 字节、39,769 个 ESC 字节)。

main 本 PR
通道实际记录的(带色)
去色后(NO_COLOR 的效果) 放行

main 即便去色也照拦,因为第 4 条腿匹配上了测试当 fixture 打印的三条普通 Error: 行——Error: boomError: Unsupported mode "midnight"…Error: Not implemented: navigation…。计数则完全不受它们影响:求和 4 个错误,4 条传输行。描述里的核心论断在真实构件上复现。

4. 真实传输超时下 Errors == timeouts(图 4)

不用手写 fixture:一个把主线程阻塞到超过 birpc 60 秒期限的 reporter,能产出真正的 Error: [vitest-worker]: Timeout calling "…" 未处理错误。五种配置,每次都真的超时(约 70 秒)、在 dangerouslyIgnoreUnhandledErrors: false 下真的退出 1。

awk 期望的 Errors N errors 形态正是 Vitest 真实输出的样子( Errors 1 error Errors 3 errors),而且在 guard 能走到计数的每一份日志上两个数都相等。唯一不等的一次(4 文件 / 4 worker,4 vs 5)是有一次传输死亡落进了 Failed Suites 区块而不是未处理错误列表——那种情况第 1 条腿先接住,失败 tally 也会兜底。

R1-1 第 6 轮那条路径实测不可达。 三条独立证据:

  1. github-actions reporter 只有在 workspace 没配 reporters 时才会被自动追加——本仓库 27 个 vitest 配置里有 20 个符合,所以前提成立。但它的 onFinished 会跳过任何栈里没有项目内帧的错误(const stack = result?.nearest; if (!stack) continue;),而传输错误的栈全是 node_modules 与 node 内部帧。在该 reporter 处于激活状态下实测:额外传输行 0 条
  2. 同一装置的对照组:真有测试失败时确实会打出 ::error file=…——说明 reporter 在跑;上面那个「没有」是结论,不是装置失灵。
  3. 仓库普查:没有任何 workspace 源码或测试文件包含字面量 [vitest-worker]: Timeout calling。含有它的只有 release.yml 本身和 scripts/tests/release-workflow.test.js,而后者走 test:scripts,不在 test:release:workspaces 里。

描述引用的那份普查我也重跑了:296 个 Error 子类,26 个(9%)不带后缀,其中 5 个把不带后缀的名字赋给了 err.nameBenchmarkFailureChannelLivenessFailureGitPullFailureProbeRunFailureSubmitRefusal)。描述里的 293/4 偏保守,论证两边都成立。

5. 变异测试 —— 14 个里死 11 个(图 5)

每个变异体都打到 release.yml 上,判据是本 PR 自己的 47 行套件。被杀掉的有:删掉计数比较、让求和只保留最后一个 summary、放开分支入口的锚、放开计数 grep 的锚、删掉通过 tally / 失败 tally / signal 三条腿、把 summary 模式收窄成单数 error、删掉它的尾锚 $、以及删掉 shell: bashNO_COLOR

存活 3 个,全是宽度/锚点类且方向都是 fail-closed:summary 求和上的 ^[[:space:]]*(M9)、通过 tally grep 上的同一个锚(M13)、以及把第 2 条腿放宽成裸 Tests(M14,属于 #10805 那条腿)。描述披露了其中两个,M13 是第三个。

有一处小出入,改注释即可、不必改代码:那行注释写着「summary 求和锚定在小节行形态上」的用例(Errors 2 errors occurred in fixture data),实际区分它的是尾锚 $ 而不是首锚——M12 会被它杀掉,M9 不会。

6. 计数仍然看不见的两条路径 —— 都复现了,也都不是回归(图 6)

A —— R5-1,确认成立。 用真实 npm run test:release:workspaces 跑两个真 workspace:ws-a 吃到一次真实传输超时并打出完整的绿 tally;ws-b 在加载配置时崩溃,头是一个真实的无后缀类名(ChannelLivenessFailure:——本仓库真的把它赋给了 err.name)。npm 不管前一个 workspace 是否失败都会继续跑后面的,于是日志收尾是绿的。main 与本 PR 都退出 0,annotation 写着「Every test passed」——而有一整个 workspace 根本没跑。

B —— 新发现,同一形态。 一次传输超时让整个测试文件的结果丢失时,会打出 Test Files 1 passed (2) / Tests 1 passed (2)。判别信息就明明白白在日志里——收集了 2 个只报了 1 个——而第 2 条腿并不读它。两臂都退出 0,半个分片就这么丢了。

两条都是既有问题:在真实字节上 main 表现完全一致,不是本 PR 引入的。真正新的地方在于它们现在可达了——在本 PR 之前 pass-through 根本不会触发,整个 step 也根本不会失败。

关于这次替换,有一处必须如实说明:计数并不是头部匹配规则的严格超集。一个在打出任何 summary 之前就崩溃、且头恰好是 Error: 的 workspace,会被 main 的第 4 条腿拦下、却被计数放行(实测:main 退 1,本 PR 退 0)。这是相对 main 设计意图的回归,不是相对其实际行为的回归——在 main 上这条路径因为本 PR 修的那两个原因,在生产中根本不可达。

一条已验证的第五条腿可以同时关掉两者,只读 npm 和 Vitest 本来就打印的东西,完全不看头部(代码见英文部分):

日志 本 PR 加第 5 条腿
生产运行 33713579913(去色) 放行 放行 —— 保留(started=23 reported=23 incomplete=0
干净传输超时、tally 完整 放行 放行 —— 保留
workspace 在任何 summary 之前就死掉(A) 放行
有一个测试文件的结果丢失(B) 放行

打上它之后本 PR 自己的 47 行全部仍然通过;生产日志里 20 个 summary 全都满足 passed + skipped == collected,所以不会因为 skip 而误红。只需要 mawk 兼容的 awk——本来就满足,仓库里另有 6 个 workflow 在用 awk。

7. 建议

  1. 合入。 它修好了一个失效的 release 门禁,让 guard 第一次真正可达,并且在真实日志上对动机运行给出了正确判断。新增的每一行都有一个删掉就变红的测试钉着。
  2. shell: bash 是线上变更 —— workspace 测试门禁从此开始拦截发版。这正是这个 step 的意义,也正是维护者该签字的地方,而不是那个正则。
  3. R1-1 第 6 轮的那种再断言不应继续压着本 PR:路径实测不可达,并且有对照组证明装置有效。
  4. R5-1 与 tally 完整性这条路径值得做后续,但不是合入条件——它们在 main 上同样存在。上面那条第 5 腿已验证,想直接并进来也可以。
  5. 两处小问题:在 tally 报出的测试数少于收集数的分片上,annotation 仍写「Every test passed」;以及 M9 那条注释把用例归给了错误的锚。

范围与限制

没有端到端跑过一次真实 release。验证针对的是逐字提取的 step 正文、真实 Vitest 3.2.7、真实 npm run --workspaces 以及真实生产日志。传输超时是靠把主线程阻塞过 birpc 期限诱发的,不是靠机器争用。ECS 自托管 runner 本身没有被驱动。去色的生产日志是把真实字节里的 ESC 序列去掉,不是在 NO_COLOR 下重跑;第 2 节的 NO_COLOR 数据来自本地新跑的运行。完整 test:scripts 有 4 个失败,都在本 PR 不涉及的文件里,在本环境(root 用户、symlink 的 node_modules)下 origin/main 上同样复现;release-workflow.test.js 本身两边都是 47 passed / 1 skipped,dc4e64ca 上 CI 全绿。


🤖 Verified with Claude Code — Claude Opus 5 (1M context)

@wenshao

wenshao commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

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: 257 passed · 0 failed · 257 total

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

中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)

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

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

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

Verification report

Sandboxed verification: ❌ not passed — findings reported (agent verdict) — follow-up round; the PR moved four commits since the last round (4d4ce9e0dc4e64ca), including a full rewrite of the guard

Scripted assertions: 257 passed · 0 failed · 257 total (extract 22 + A/B 125 + real-bytes 44 + mutation 19 + survivors 28 + rounds 4 + lints 15)

Verified head dc4e64ca018d243a61daac3e18528dc515de9737 (git rev-parse HEAD^2) · base 9b188ea38234466ce7fe7df6c689ec093143c929 (HEAD^1) · merge 327c76a4f882ceef5b792a503b5eecfb9957fdc7

中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)· 复核轮(head 已前进 4 个 commit,guard 整体重写)

本轮是复核轮:上一轮 head 为 4d4ce9e0(放宽 header 正则的方案),本轮 head dc4e64ca 已把判据换成 Vitest 自己发布的计数Errors N errors 求和 vs [vitest-worker]: Timeout calling 行数),并新增 shell: 'bash'NO_COLOR: 'true' 两条 step 声明。按规则所有测量全部重新执行,未沿用旧报告数字。

结论:findings(257 条脚本化断言,0 失败)。

A/B 结论:用真实 vitest(阻塞主进程 66 s 越过 birpc 的 60 s 超时,产出真实的 [vitest-worker]: Timeout calling 行)构造日志:base 臂(pipefail 修好后的 base guard)把"真实 transport 超时 + 真实无后缀崩溃(PoolTimeout:)"的日志放行 exit 0,HEAD 拦住 exit 1(01-real-vitest-bytes-base-vs-head.png);合法放行(errors==timeouts==1)在 HEAD 上仍然成立。20 条 committed 行 × 4 个 build = 80 cell,17 个 sibling fixture × 4 build = 68 cell,全部与预测一致。

上轮发现状态:F1(ANSI 使 guard 不可达)已修NO_COLOR: 'true',实测彩色 260 ESC 字节 vs 纯文本 0);F4(第 3 条判据无行钉住)已修(新增行,M6 被杀);F3/F5/F6 随 header 正则删除而被取代,但 F5/F6 的同类缺口以新形式重现(M9 冗余防御、M16 未钉住且为静默放行方向);F2 变窄:描述里"上报崩溃的 RPC 超时则崩溃不被计数"的说法被真实字节证伪(Vitest 仍单独计数,errors=3 vs timeouts=2)。

新发现:N1 锚点只认 [vitest-worker]:,shipped vitest 还有 [vitest-pool]:(4 处)与 [vitest-api]:(1 处)两条同型通道,落到 else 分支后注解声称"日志里没有 transport 超时"——与日志内容相反;N2 计数可被测试自身打印的行吹大,而真实 vitest 把 console.log 原样回显在第 0 列(实测),因此测试回显一行与 Vitest 摘要逐字节相同的 Errors 2 errors 就会拒绝一次本该放行的通过(base 0 → HEAD 1);N3 一个被 SIGKILL 的 workspace 不产生任何摘要,两个计数都看不见它,早前 workspace 的干净 transport 超时仍会放行(base 与 HEAD 均 0);N4 第 2 条判据放宽成裸 Tests 的变异存活,行为上会让"全部 skip"的 shard 静默放行。

未覆盖:无真实 release 端到端;无 token,无法取 run 33713579913 的真实日志校准;[vitest-pool]/[vitest-api] 超时在生产中能否以"有 tally"的形态出现未证明;全量 scripts/tests 套件对照在报告时仍在运行(见 Not covered)。

Previous-round findings, re-measured at the new head

Every row below was re-measured at dc4e64ca, not diffed from the old report. The old head's header-regex guard no longer exists, so "superseded" rows were re-measured by driving their shape through the new guard.

# finding (previous round) severity status at dc4e64ca re-measurement
F1 the guard's branch is unreachable in the lane: Vitest colours from the mere presence of CI, escapes sit between every label and its value, so no anchored leg matches Suggestion fixed NO_COLOR: 'true' added to the step. Real run pair: colour regime 260 ESC bytes over 42 lines, Tests…passed leg MISS, awk Errors sum 0 despite 3 real unhandled errors; the same run with NO_COLOR 0 ESC bytes, leg HIT, sum 3 (04-colour-vs-nocolor-leg-census.png). Deleting NO_COLOR (M12) turns the committed suite red.
F2 the leg's own grep -qv 'Timeout calling' filter swallows the crash vitest embeds in the timeout line, so a real crash ships green Suggestion stands, narrower than stated On REAL bytes the shape the description calls invisible (Timeout calling "onUnhandledError" with "worker pool exhausted") was counted separately by vitest: Errors 3 vs timeouts 2, and HEAD refused (see Correction C1). The residual hole now requires the crash to never reach the reporter at all — S4 (Timeout calling "onUnhandledError" with "msg" + Errors 1 error) and S5 (a SIGKILLed workspace) still pass through on both arms.
F3 Node's [cause]: TypeError: prefix puts nested-cause crashes outside both matchers Suggestion superseded The header matcher is gone; the count never reads a header. The real crash block (S12) flips base 0 pass-through → HEAD 1.
F4 leg 3 (the failing-tally check) has no committed row and is load-bearing Suggestion fixed New row passing tally in one workspace, failing tally in another; M6 (delete leg 3) is now killed by the committed suite.
F5 the matcher's ^[[:space:]]* indentation tolerance is unpinned Nice to have superseded, analogue re-measured No header matcher remains. The analogous gap is M9 (the awk Errors sum's start anchor): survives the suite, but is redundant defence in depth with the awk $2 field selection — 37/37 corpus fixtures byte-identical with the anchor deleted; the residual is only a line whose $2 is numeric and which ends Errors N error(s).
F6 nothing pins the matcher's upper bound (a widening survives) Nice to have superseded, analogue re-measured No header matcher remains. The analogous gap is M16 (leg 2 loosened to a bare Tests): survives the suite and is not fail-closed — it grants a pass-through on an all-skipped shard (finding N4).

The previous round's five corrections stand as reported; none of them is contradicted by this round's measurements.

Central claim and the A/B

Central claim. The workspace_tests step may exit 0 over a Vitest worker-RPC timeout only with proof the run reached its end and that every unhandled error Vitest counted was the transport — certified by comparing vitest's own Errors N errors whole-file sum against the count of [vitest-worker]: Timeout calling lines — and that proof is live in production because the step now runs with shell: 'bash' (pipefail) and NO_COLOR: 'true' (plain-text log).

The PR bundles three changes, so the A/B is a four-build decomposition, each cell run under the shell contract its own YAML declares (shell: bashbash --noprofile --norc -eo pipefail {0}; absent → GitHub's linux default bash -e {0}):

build script shell contract meaning
V0 base guard bash -e main today
V1 base guard bash --noprofile --norc -eo pipefail pipefail only
V2 head guard bash -e guard rewrite only
V3 head guard bash --noprofile --norc -eo pipefail HEAD, all three

The money cells — REAL vitest bytes, no stubs on the producer side. The transport timeout is real: a custom reporter busy-waits the main process for 66 s, past birpc's DEFAULT_TIMEOUT of 60 000 ms (node_modules/vitest/dist/chunks/index.B521nVV-.js:3), so vitest itself raises Error: [vitest-worker]: Timeout calling "snapshotSaved". Witness: 01-real-vitest-bytes-base-vs-head.png; raw logs logs/real-block-*.log.

real log (vitest 3.2.7, this container) V0 V1 V2 V3
transport death only, NO_COLOR (Errors 1 / timeouts 1) 0 0 0 0 pass-through (relief preserved)
transport death + real suffix-less crash PoolTimeout: (Errors 3 / timeouts 2), NO_COLOR 0 0 pass-through — a real crash ships green 0 1 blocked
the same run, CI with NO NO_COLOR (the lane before this PR) 0 1 0 1
crash only, no transport death, NO_COLOR 0 1 0 1
crash only, CI with NO NO_COLOR 0 1 0 1

V0 and V2 exit 0 on every real failure log: without pipefail the pipeline's status is tee's, the || handler never fires, and the step reports success over a genuine crash. That is the defect shell: 'bash' fixes, and the guard rewrite alone (V2) cannot revive it — the two changes are individually necessary, which a two-cell A/B could not show.

Fixture corpus. 20 committed rows lifted verbatim out of each arm's own copy of scripts/tests/release-workflow.test.js (bracket-matched, evaluated with testStep rebound to my extraction) plus 17 sibling fixtures of my own, each through all four builds: 20 × 4 + 17 × 4 = 148 cells, every cell matching its pre-declared prediction (logs/ab.json). Calibration: my extraction + the repo's own harness contract (bash -e -o pipefail -c) reproduces all 6 base rows and all 20 head rows exactly, including annotation text. Witness for the sibling doors and the pipefail control: 03-sibling-doors-and-pipefail-control.png.

Mutation matrix. 18 single-point mutants of the HEAD release.yml, each applied to the merge-tree worktree and run against the committed suite; every mutation verified applied (anchor unique, post-edit text changed, YAML still parses). Witness: 02-mutation-matrix-kills-and-survivors.png.

result mutants
control M0 GREEN 48 passed (48) — the command does collect the file the mutants edit
killed (14) M1 count comparison · M2 total = $2 · M3 unanchored branch · M4 unanchored count · M5 passing-tally leg · M6 failing-tally leg · M7 signal leg · M8 singular error · M10 awk end anchor · M11 shell: 'bash' · M12 NO_COLOR · M13 both interpolated counts · M14 awk $1 · M15 positive control exit 0exit 3
survived (4) M9 awk start anchor · M16 leg 2 → bare Tests · M17 widen to three RPC channels · M18 anchor the count

The PR's claimed "8 of 8 mutants die" reproduces: M1–M8 are exactly its eight, all killed, each to its own row. Every survivor is adjudicated behaviourally below (28 assertions, logs/survivor-run.log), each with a collateral sweep over all 37 fixtures.

Corrections to the PR description

These are corrections to what the text says, not requests to change code.

  1. C1 — "When the RPC that was reporting a crash is the thing that timed out, the crash is never counted, so Errors and the transport count agree and the run passes through." Disproved on real bytes. My real run contains exactly that line (Error: [vitest-worker]: Timeout calling "onUnhandledError" with "worker pool exhausted", logs/real-block-crash2.log:37) and vitest still counted the crash as its own unhandled error: Errors 3 errors against timeouts 2, so the counts disagreed and HEAD refused. The mechanism is that birpc's timeout fires on the caller while the posted message is still handled by the main process. The residual hole is real but narrower: it needs the crash to never reach the reporter at all (S4/S5, finding N3).
  2. C2 — "18 rows." The committed table at HEAD has 20 rows (6 pre-existing + 14 added across the branch). The description's own table lists 16. A reviewer walking the plan step by step will not find the row numbering it implies.
  3. C3 — refinement of "Two mutants that survive are pre-existing and not this leg … (its direction is safe — unanchored is strictly broader)". True of the awk start anchor (M9, and only because awk $2 defends it in depth). Not true of the other disclosed survivor: loosening leg 2 from Tests[[:space:]]+[0-9]+ passed to Tests is not fail-closed — it grants the pass-through on a shard where every test was skipped (M16 probe: head 1 → mutant 0). Of the two disclosed survivors, one is benign-direction and one is silent-green-direction.
  4. C4 — correction to the previous round's refinement 4 ("5 findings on the substituted bytes, byte-identical base vs head"). Re-measured with shellcheck 0.11.0 and the repo's flags: 6 findings per arm, and the rule multiset is not identical — head carries one more SC2292 (Prefer [[ ]] over [ ], the new [ "${errors}" -eq "${timeouts}" ] test) and one fewer SC2312 (the removed header pipeline's masked return value). Both are style notes and neither is error-severity beyond SC2148, which is an artifact of extracting a shebang-less block (the real step runs under shell: bash). The previous round's conclusion — no new lint of consequence — still holds; the mechanism and the counts did not.

Findings

N1 — Suggestion: the [vitest-worker]: anchor excludes two other shipped vitest RPC channels, and the fallthrough annotation then asserts the opposite of the log's contents

The shipped vitest 3.2.7 raises Timeout calling from three distinct channels: [vitest-worker]: (node_modules/vitest/dist/chunks/rpc.-pEldfrD.js:49), [vitest-pool]: (coverage.DfSpMS-b.js, four sites) and [vitest-api]: (cli-api.DVe0nWUx.js:5180). Base's unanchored grep -q 'Timeout calling' entered the pass-through branch for all three; HEAD's anchor enters it for one. A log carrying a pool or api transport death beside a clean tally now falls to the else branch, whose annotation reads No FAIL line and no transport timeout in the log — while the log does contain a transport timeout (S1/S2: V1 0 pass-through → V3 1, annotation asserted wrong by scripted check).

Bound. The exit-code direction is fail-closed (1, not 0), and I could not demonstrate that a pool/api death presents as a pass-through-worthy log at all: those throws sit on channels whose realistic failure mode is a main-process crash with no summary, which both arms refuse. So today the demonstrable defect is the wrong diagnosis text and the unproven narrowing of the #10805 relief; the shape's production reachability is unknown without the run logs this job cannot fetch.

Measured candidate fix (M17). Widening branch and count to \[vitest-(worker|pool|api)\]: restores the pass-through when the death is counted in Errors (probe head 1 → mutant 0), corrects the annotation on exactly S1 and S2 (status unchanged, annotation-only), and preserves the anchor's purpose — a test's bare Timeout calling words still route to unexplained (head 1, mutant 1). Collateral: 35/37 byte-identical. The committed suite does not pin it (M17 survives), so it should ship with a row carrying a [vitest-pool]: death plus its Errors summary.

ART=tmp/pr10902-verify-20260906-010540
node "$ART/harness/survivors.mjs" "$ART" | sed -n '/^M17 /,/^ok   M17 collateral/p'
node "$ART/harness/ab.mjs" "$ART" | grep -A4 -E '^FLIP \| S[12] '

N2 — Suggestion: the count is inflatable by anything a test prints, and real vitest echoes console.log at column 0 — so a test can refuse a pass-through every test earned

The count's two greps are unanchored by design (the anchor prevents a test's words from granting the pass). The other direction is open: real vitest echoes a test's console.log at column 0 with no prefix and no indentation (measured: logs/real-console-echo.log lines 8–11), so a test printing Errors 2 errors or Error: [vitest-worker]: Timeout calling "onTaskUpdate" contributes to the sum exactly as if vitest had printed it. S6 and S13 (both with proven producer shapes) flip V1 0 pass-through → V3 1: a shard whose only unhandled error was the transport, in which some test merely logged a summary-shaped line, is refused with the annotation "the two counts disagreeing for a reason this log does not show".

This is the same class of false-red the PR says it removes for headers ("ordinary Error: lines are test output, not evidence … the count is unmoved by them") — true for header-shaped lines, not for summary-shaped ones. Fail-closed (a blocked release, not a green one), so it is a tradeoff to name rather than a blocker. The committed row that was meant to cover this (transport timeout, and a summary-shaped line a test printed) uses a fixture ending occurred in fixture data, which the awk $ anchor already excludes — so it pins the $ anchor (M10 killed) and not the echo shape (no row carries a byte-identical echo).

No fix exists in the obvious direction (measured). M18 — anchoring the count to ^[[:space:]]*(Error: )?\[vitest-worker\]: — is a dead candidate: 37/37 corpus fixtures byte-identical, because the real echo is at column 0 and any anchor that tolerates vitest's own indented Unhandled Errors rendering also tolerates it. Reported as a negative so the next round does not re-propose it.

ART=tmp/pr10902-verify-20260906-010540
grep -nE 'Errors|Timeout' "$ART/logs/real-console-echo.log"   # column-0 echoes, real vitest
node "$ART/harness/ab.mjs" "$ART" | grep -A4 -E '^FLIP \| S(6|13) '

N3 — Suggestion (pre-existing, unchanged): a workspace that dies without printing a summary is invisible to both counts, so an earlier clean transport death still passes the run through

npm run test:ci --workspaces writes every workspace's output into one log and stops at the first failing one. A workspace killed by SIGKILL/OOM prints no tally, no header and no Errors line, so it contributes nothing to either side of the comparison; an earlier workspace's transport death then satisfies errors == timeouts and the step exits 0 (S5: V1 0, V3 0). The description's "what the count still cannot see" names only the onUnhandledError-timeout shape; this one is the same structural limit — evidence that never reached vitest's reporter — and it is silent-green, not fail-closed. Pre-existing on base, so not a regression; listed because the count is presented as closing the class.

N4 — Nice to have: leg 2's Tests[[:space:]]+[0-9]+ passed is unpinned, and its loosening is silent-green

M16 (→ bare Tests) survives the committed suite. Behaviourally it grants the pass-through on a shard whose tally is Tests 3 skipped (3) (probe head 1 → mutant 0; collateral 36/37 identical, the changed fixture being exactly S14). The leg as written is correct; what is missing is the row. One line closes it: a fixture carrying a transport death, Tests 3 skipped (3) and Errors 1 error, expecting exit 1.

N5 — Nice to have: the awk Errors sum's ^[[:space:]]* start anchor is unpinned, but is defended in depth by the $2 field selection

M9 survives the suite and changes nothing on the 37-fixture corpus, because on any line where Errors N error(s) is not the first field, awk's $2 is a word and coerces to 0. The residual is narrow and fail-closed: a line whose second field is numeric and which ends Errors N error(s) (probe Retried 3 times, Errors 2 errors: head 0 → mutant 1). Classification: redundant defence, correct as it stands — not a test to write first, and not code to delete, since the residual is real.

Targeted gates

gate result liveness proof
changed file scripts/tests/release-workflow.test.js at the merge ref 48 passed (48), exit 0, three identical rounds — (the mutation matrix ran the same file 19× with deterministic outcomes; M0 green)
bash -n on both verbatim extracted step scripts clean planted unterminated if reported, exit nonzero
shellcheck 0.11.0 with the repo's flags on the substituted bytes 6 findings per arm; rule multiset shifts +1 SC2292 / −1 SC2312 (the rewritten leg), both style notes; no error-severity finding beyond SC2148, the missing-shebang artifact of extracting a shebang-less block planted unguarded cd reported SC2164
actionlint 1.7.12 with the repo's flags on base/head/merge trees exit 0 on all three planted invalid if: expression reported, exit nonzero
node scripts/lint.js --actionlint / --shellcheck over the whole repo at HEAD exit 0 both working tree byte-clean afterwards (no prettier --write ran)
full scripts/tests suite, base-tree vs merge-tree see Not covered

Not covered

  • No release was run end to end, and this job has no GitHub token, so release run 33713579913's real log — the artifact that would calibrate the replay against production bytes — remains unfetched. The replay is calibrated against the repo's own instrument instead (26/26 committed rows reproduced through the independent extraction).
  • The full scripts/tests suite comparison (base-tree vs merge-tree) was still running when this report was written (the base arm alone exceeded nine minutes in this container). It is therefore not cited as evidence; the changed file's own suite, run 22 times across the mutation matrix and the explicit rounds, is the gate that actually covers this diff.
  • No real [vitest-pool]: / [vitest-api]: transport death was produced. N1's shapes are fixtures whose byte form is lifted from the shipped sources; whether those channels can present as a pass-through-worthy log in production is unproven (see N1's bound).
  • The self-hosted ECS runner's environment (whether it exports NO_COLOR or CI) is not measurable from this container; the colour conclusion is stated for any runner where CI is present and NO_COLOR absent, which is the configuration release.yml declared before this PR.
  • yamllint was not run: node scripts/lint.js --setup fails with pip3: Permission denied in this container, proven environmental by running the setup.
  • Not run: npm run typecheck, ESLint, prettier checks, every workspace unit suite, all integration suites. The diff contains no TypeScript and ESLint does not cover YAML.
  • Per-commit attribution is out of reach (depth-2 shallow checkout; the snapshot lists 7 commits, git rev-list HEAD^1..HEAD^2 returns 1). The aggregate HEAD^1..HEAD diff is what is verified; the evolution is read from the commit messages, reported-not-asserted.
  • The PR's census claim ("26 of this repo's 293 Error subclasses have suffix-less names") was not re-derived; at HEAD it is historical justification for a matcher that no longer exists.

Methodology

Environment: the CI verify container (node v22.23.2, GNU bash 5.2.15, GNU grep 3.8, vitest 3.2.7 at the root plus a nested 1.6.1 in packages/sdk-typescript), working tree at the merge ref 327c76a4, npm ci/npm run build pre-completed. Scratch worktrees for the base tip, the PR head and the merge commit live under tmp/pr10902-verify-20260906-010540/; the PR touches no dependency file, so all arms share the root node_modules, and the only internal package the harnesses resolve is yaml (verified to resolve to the root install, which the diff does not touch). Eight harnesses drove the code, all re-runnable as node harness/<name>.mjs <artifact-dir>: extract.mjs (YAML-parse the step verbatim, assert re-indentation fidelity and the declared shell contract), ab.mjs (four builds × 37 fixtures, with a leg-level census using the real grep/awk), real.mjs (five logs from REAL vitest runs, including a real worker-RPC timeout produced by blocking the main process past birpc's 60 s limit, driven through both arms), mutation.mjs (18 mutants against the committed suite in the merge-tree worktree, each verified applied and the tree restored byte-identical), survivors.mjs (behavioural adjudication plus 37-fixture collateral sweeps), rounds.mjs, lints.mjs (the repo's own lint flags with the binaries scripts/lint.js --setup installed), gates.mjs. Raw per-cell logs, per-mutant vitest output, every real vitest capture and each harness's JSON live under logs/; the four evidence images are in evidence/. Assertion counts come only from those harnesses' own tallies; fail counts unexpected outcomes only, and the seven mid-round harness prediction errors (my own wrong expectations for the no-pipefail builds and three mis-designed survivor probes) were fixed and re-run before any number was reported — the corrected expectations are what the counts record.

Nothing was posted to GitHub; no gh call was made. The three scratch worktrees are removed at the end of the round.

Qwen Code · sandboxed verification

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

01-real-vitest-bytes-base-vs-head

02-mutation-matrix-kills-and-survivors

03-sibling-doors-and-pipefail-control

04-colour-vs-nocolor-leg-census

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

Qwen Code · sandboxed verification

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

APPROVE (verified at head dc4e64c)

Why a pass despite the two Criticals the flow re-asserted here

Both standing items (R1-1: a contrived log can make the transport-line count agree with the error count via an echoed/fabricated phrase; R5-1: a workspace that dies before printing ANY summary is invisible to the whole-file count parity when a different workspace contributes the transport line) were verified against the head code and I reach the same characterization the flow and author already agreed on: they are residual limitations of a guard this PR is net-tightening, not holes this PR opens. The comparison that settles it is against what is on main today:

  • On main the entire || { … } handler is dead code — the step's default bash -e {0} supplies no pipefail, so npm … | tee yields tee's 0 and a shard with genuinely failing tests exits 0 and the release proceeds (the PR names the measured run: 33806806226). This PR's shell: 'bash' line closes a worse false-pass than either open finding — and with NO_COLOR: true it closes the same guard being blind on coloured real-CI bytes (escapes between label and value defeat every anchored pattern; the PR measured three of four conditions returning 0 on release bytes).
  • Pre-PR, the residual-pass test was ! grep 'Error:' | grep -v 'Timeout calling' over producer-chosen headers, which the PR's own evidence shows is incomplete by construction; the count-parity replacement refuses on strictly more shapes (any errors≠timeouts disagreement, signal death, missing passing tally, failing tally, zero-summary logs all refuse — I walked each condition at head: every fabricated-line route inflates timeouts and thus BREAKS equality toward refusal, the fail-closed direction).

So neither standing Critical is a correctness regression, security hole, data-loss path, or compat break introduced here — they are improvement-over-main with a named residue. The residue's disposition is also already decided upstream: both threads end at an explicit maintainer design decision (anchor-precisely / fail-closed-on-any-error / consume Vitest's structured output for provenance), the author's holds each name one, and the human maintainer approved this exact head at 00:26Z after those holds — that is the acceptance the flow asked for, not a bypass of it. I recorded one correction against the flow's note: R5-1's fix suggestion carries its own stated precondition (verify the banner shape against real release bytes) that no review environment here satisfies, which is exactly why landing it blind would be the wrong closure.

Historical items (earlier rounds)

The three earlier CHANGES_REQUESTED cycles each pre-date their own fixes (pipefail, color, trigger-narrowing are this PR's responses to them); the round-5 Critical pair is the only residue and is dispositioned above. Deferred S items ([vitest-pool]/[vitest-api] producers, pattern duplicated in two regex dialects) are non-blocking by both gradings and fold into the same design decision.

CI at head

17 green, zero failures — including Test (ubuntu-latest), the lane running the 174 new pinning lines of release-workflow.test.js; one fleet-side smoke cancellation, non-attributable.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship. ✅

@wenshao
wenshao added this pull request to the merge queue Sep 6, 2026
Merged via the queue into main with commit 689453e Sep 6, 2026
421 of 429 checks passed
wenshao added a commit to aniruddhaadak80/qwen-code that referenced this pull request Sep 6, 2026
…ulated (QwenLM#11157)

The ratchet tolerates 4096 bytes of growth per PR without a baseline
bump, which is how drift is supposed to stay reviewable — but two
consecutive release.yml PRs (QwenLM#11127, QwenLM#10902) each grew the file by
roughly 2.5 KB, each individually inside the allowance, so neither was
forced to touch the recorded number and the sum (5160) sailed past it.
Since then every strict gate run — any local run without a PR base SHA,
and any future PR that touches release.yml — fails on growth it did not
author, exactly the red wall the QwenLM#9904 leniency exists to keep off
unrelated PRs. Record the measured size (75392, wc -c on main) as the
gate's own error message prescribes.
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.23.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants