Skip to content

fix(ci): give the scripts test suite the shared-ECS timeout ceiling (#10853) - #10858

Open
qwen-code-dev-bot wants to merge 6 commits into
mainfrom
autofix/issue-10853
Open

fix(ci): give the scripts test suite the shared-ECS timeout ceiling (#10853)#10858
qwen-code-dev-bot wants to merge 6 commits into
mainfrom
autofix/issue-10853

Conversation

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

What this PR does

Gives the repository's script-test suite the same raised timeout ceiling on the shared self-hosted ECS pool that every other suite already has, and removes seven per-test ceilings in the autofix workflow contract suite that duplicated the old flat value and therefore shadowed the new one. A pin in the config-parity suite holds both branches of the new ceiling. No assertion, fixture, or production code changes: the only behavioral difference is how long a test may run on ecs-qwen-* runners before vitest calls it a timeout.

Why it's needed

The v0.23.0 release run (33676423730) failed its quality gate and the release was not published. quality is only the aggregator; the lanes that actually failed were the three workspace-test shards and the script-test lane. The script lane published a named failure: scripts/tests/qwen-autofix-workflow.test.js:13536 > upserts deferred findings into a per-PR issue that survives the mergeError: Test timed out in 30000ms.

That test is not hung. It replays the real deferred-findings upsert script against a recording gh stub sixty times, each call a fresh temp directory plus a bash subprocess tree, and it needs 22.9s on an idle host — 76% of the 30s budget it ran under. On the shared pool, where the release lane runs it with four vitest workers beside the other release jobs, ordinary scheduling delay is enough to cross 30s.

Every other suite in this repository already treats that as expected and raises its ceiling when RUNNER_NAME starts with ecs-qwen-: packages/core, packages/cli and packages/acp-bridge (#8982, #10552), packages/vscode-ide-companion (#10672), and packages/web-shell (#10765, merged about fifteen hours before this release run). scripts/tests/vitest.config.ts was the one left with a flat 30s — and it is the most subprocess-heavy suite in the repo, run by both the release quality_scripts lane and main CI's npm run test:ci.

Seven tests in that contract suite also declared their own , 30000) ceiling. Those values were byte-identical to the suite default, so they were dead weight, and because a per-test ceiling outranks the config they would have shadowed the clamp exactly where it was needed. This PR removes them and leaves the deliberately tighter , 20000) and , 10000) ceilings alone.

Reviewer Test Plan

How to verify

  1. Measure the healthy runtime of the test that failed the release: npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js -t "upserts deferred findings into a per-PR issue that survives the merge" → passes in ~23s on an idle host, i.e. it always was a ceiling problem, not a hang.
  2. Confirm only timeout plumbing changed: git diff touches three files under scripts/tests/ and no assertion.
  3. Confirm the clamp reaches the suite and is pinned: npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/unit-vitest-configs.test.ts → 27 passed. Then flatten 60_000 to 30_000 in scripts/tests/vitest.config.ts and re-run → the new pin fails with RUNNER_NAME=ecs-qwen-parity: expected 30000 to be 60000; restore it and the suite is green again.
  4. Confirm a per-test ceiling outranks the config, which is why the seven had to go: in a scratch test file under scripts/tests/, write one test that sleeps 35s with no explicit ceiling and one that sleeps 2s with , 1000). With RUNNER_NAME unset the 35s test fails at Test timed out in 30000ms; with RUNNER_NAME=ecs-qwen-probe it passes at ~35s, while the , 1000) test fails at 1000ms in both arms.
  5. Run the whole lane: npm run test:scripts. Expect the pre-existing, environment-dependent verify-capture pixel failure described under Risk & Scope if your sandbox renders fonts differently; every other test passes.

Evidence (Before & After)

Before — the ceiling the release lane ran under, reproduced on a contended host (a concurrent full workspace-suite run, load ~150 on 64 cores):

❯ scripts/tests/qwen-autofix-workflow.test.js:13536:5
Test Files  1 failed (1)
     Tests  1 failed | 228 skipped (229)

After — same host, same test, RUNNER_NAME=ecs-qwen-probe. Reported honestly: under that deliberately extreme load (~8.5x slowdown, 194s of test wall time) even the raised ceiling is not enough. The clamp is headroom, not immunity — it moves the margin from 1.3x to 2.6x, which is the margin maintainers already accepted for cli, core, acp-bridge, web-shell and vscode-ide-companion. The mechanism itself is proven by the scratch-test matrix in step 4 above (35s test red at 30s, green at 60s) and by the mutation probe in step 3.

healthy runtime ceiling on ecs-qwen-* headroom
Before 22.9s 30s (flat, all runners) 1.3x
After 22.9s 60s 2.6x

Tested on

OS Status
🍏 macOS ⚠️ not tested — the changed branch only triggers on ecs-qwen-* runners and the non-ECS value is unchanged
🪟 Windows ⚠️ not tested — same reason; this suite's bash-driven workflow files are already excluded on win32
🐧 Linux ✅ tested — self-hosted runner, npm run build, npm run typecheck, npm run lint, npm run test:scripts, plus the reproduction and mutation probes

Environment (optional)

Unit tests only, on a self-hosted Linux runner with RUNNER_NAME unset (so the non-ECS branch is what the local runs exercised) plus explicit RUNNER_NAME=ecs-qwen-* stubs for the ECS branch. No CLI, sandbox, or network-dependent flow was run.

Risk & Scope

  • Main risk or tradeoff: a genuinely hung test in this suite now takes 60s instead of 30s to report on the ECS pool. Assertions still fail instantly — only the ceiling moves. Each spawnSync in the affected harness keeps its own 30s bound, so a hung subprocess is still cut at 30s. The non-ECS value is byte-for-byte what it was, so local runs and the macOS/Windows lanes are unaffected.
  • Not validated / out of scope: the three workspace-test shards that failed in the same release run. Their job logs are not readable without admin rights (403 Must have admin rights to Repository.) and their annotations carry no test names, so no suite can be named from this checkout. Release re-runs the unit suite main CI already ran: reuse a green CI verdict for the duplicated lane #10820 already holds the parsable-log analysis of that lane — 27 distinct failing files across 19 quality-failed runs with only 2 repeats, recent ones 5000ms timeouts on the shared pool — and its proposed remedy (skip the release re-run when main CI is already green for the SHA) lives in the release workflow. Stable releases also deliberately get no VITEST_RETRY, so a single flake blocks the release. Also left alone, with no evidence they failed this run: thirteen small workspace configs (packages/channels/*, integrations/*, audio-capture, chrome-extension, webui) that still run at vitest's 5s default with no ECS clamp; the exhaustive 65536-code-unit case in packages/acp-bridge/src/json-string-bytes.test.ts, which needed 5.8s under parallel load locally against that 5s default; and three redundant , 30_000) ceilings in scripts/tests/upload-aliyun-oss-assets.test.js of the same class as the seven removed here.
  • Breaking changes / migration notes: none. npm run test:scripts on this machine reports one failure, scripts/tests/verify-capture.test.js > renders 256-colour and truecolor via the default-grey fallback, which is pre-existing and environment-dependent: it fails identically under the base config (git show HEAD:scripts/tests/vitest.config.ts) and under this branch's, in ~200ms, as a PNG pixel-colour assertion rather than a timeout.

Linked Issues

Part of #10853 — this fixes the quality_scripts lane that failed in release run 33676423730 and is the only lane of that run whose failing test is publicly named. The three workspace-test shards that failed in the same run are not addressed here, so the release would still have failed on them; #10820 tracks that lane. Deliberately no closing keyword, so the release-failure issue stays open until the shard lane is settled too.

中文说明

这个 PR 做了什么

给仓库的脚本测试套件补上其他所有套件早已具备的能力:在共享的自托管 ECS 机器池上抬高超时上限;同时移除 autofix workflow 契约测试套件中七个与旧的固定值重复、因而会遮蔽新上限的单测试超时。配置一致性套件中新增一个断言,固定新上限的两个分支。没有改动任何断言、fixture 或生产代码:唯一的行为差异是,在 ecs-qwen-* runner 上一个测试在被 vitest 判定为超时之前可以运行多久。

为什么需要

v0.23.0 的发布运行(33676423730)在 quality 门禁上失败,发布没有产出。quality 只是聚合任务;真正失败的是三个 workspace 测试分片和脚本测试通道。脚本通道给出了具名失败:scripts/tests/qwen-autofix-workflow.test.js:13536 > upserts deferred findings into a per-PR issue that survives the merge —— Error: Test timed out in 30000ms.

这个测试并没有卡死。它用记录型 gh 桩真实回放 deferred-findings upsert 脚本六十次,每次都要新建临时目录并拉起一棵 bash 子进程树,在空载机器上需要 22.9 秒 —— 占它当时所处 30 秒预算的 76%。在共享机器池上,发布通道以四个 vitest worker 与其他发布任务并行运行它,普通的调度延迟就足以越过 30 秒。

仓库里其他所有套件都已经把这视为预期行为,并在 RUNNER_NAMEecs-qwen- 开头时抬高上限:packages/corepackages/clipackages/acp-bridge#8982#10552)、packages/vscode-ide-companion#10672),以及 packages/web-shell#10765,比本次发布运行早约十五小时合并)。scripts/tests/vitest.config.ts 是唯一还停留在固定 30 秒的 —— 而它是仓库里子进程最密集的套件,发布侧的 quality_scripts 通道和主 CI 的 npm run test:ci 都会运行它。

该契约套件中还有七个测试各自声明了 , 30000) 上限。这些数值与套件默认值逐字节相同,因此是无效负载;而由于单测试上限优先于配置,它们恰好会在最需要的地方遮蔽新的上限。本 PR 移除它们,并保留刻意更紧的 , 20000), 10000) 上限不动。

审阅测试计划

如何验证

  1. 测量那个让发布失败的测试的健康耗时:npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js -t "upserts deferred findings into a per-PR issue that survives the merge" → 在空载机器上约 23 秒通过,也就是说这一直是上限问题,不是卡死。
  2. 确认只改了超时管线:git diff 只涉及 scripts/tests/ 下的三个文件,且没有任何断言变化。
  3. 确认上限确实作用到该套件并被固定:npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/unit-vitest-configs.test.ts → 27 通过。然后把 scripts/tests/vitest.config.ts 里的 60_000 压平为 30_000 再跑一次 → 新增断言以 RUNNER_NAME=ecs-qwen-parity: expected 30000 to be 60000 失败;恢复后套件重新变绿。
  4. 确认单测试上限优先于配置(这正是那七个必须移除的原因):在 scripts/tests/ 下写一个临时测试文件,其中一个测试 sleep 35 秒且不带显式上限,另一个 sleep 2 秒并带 , 1000)RUNNER_NAME 未设置时,35 秒的测试以 Test timed out in 30000ms 失败;RUNNER_NAME=ecs-qwen-probe 时它在约 35 秒通过,而带 , 1000) 的测试在两种环境下都在 1000ms 失败。
  5. 运行整条通道:npm run test:scripts。如果你的沙箱字体渲染不同,预期会出现 Risk & Scope 中描述的、既有的且依赖环境的 verify-capture 像素失败;其他测试全部通过。

证据(改动前后)

改动前 —— 发布通道当时所处的上限,在一台有争用的机器上复现(并行运行完整 workspace 套件,64 核上 load ~150):

❯ scripts/tests/qwen-autofix-workflow.test.js:13536:5
Test Files  1 failed (1)
     Tests  1 failed | 228 skipped (229)

改动后 —— 同一台机器、同一个测试、RUNNER_NAME=ecs-qwen-probe。如实说明:在那种刻意制造的极端负载下(约 8.5 倍减速,测试墙钟 194 秒),连抬高后的上限也不够。这个上限是余量,不是免疫 —— 它把裕度从 1.3 倍提到 2.6 倍,也就是维护者已经为 cli、core、acp-bridge、web-shell 和 vscode-ide-companion 接受的裕度。机制本身由上面第 4 步的临时测试矩阵(35 秒测试在 30 秒红、在 60 秒绿)和第 3 步的变异探针证明。

健康耗时 ecs-qwen-* 上的上限 裕度
改动前 22.9s 30s(所有 runner 固定) 1.3x
改动后 22.9s 60s 2.6x

已测试平台

OS Status
🍏 macOS ⚠️ 未测试 —— 改动的分支只在 ecs-qwen-* runner 上触发,非 ECS 数值未变
🪟 Windows ⚠️ 未测试 —— 同上;该套件的 bash 驱动 workflow 文件在 win32 上本来就被排除
🐧 Linux ✅ 已测试 —— 自托管 runner,npm run buildnpm run typechecknpm run lintnpm run test:scripts,外加复现与变异探针

环境(可选)

仅单元测试,在一台自托管 Linux runner 上进行,RUNNER_NAME 未设置(因此本地运行实际走的是非 ECS 分支),另外用显式的 RUNNER_NAME=ecs-qwen-* 桩验证 ECS 分支。没有运行 CLI、沙箱或依赖网络的流程。

风险与范围

  • 主要风险或取舍:该套件中一个真正卡死的测试,现在在 ECS 机器池上需要 60 秒而不是 30 秒才会上报。断言仍然立即失败 —— 只有上限变化。相关测试框架中的每个 spawnSync 仍保留自己的 30 秒限制,因此卡死的子进程仍会在 30 秒被切断。非 ECS 数值与原来逐字节相同,所以本地运行和 macOS/Windows 通道不受影响。
  • 未验证 / 范围之外:同一次发布运行中失败的三个 workspace 测试分片。没有管理员权限就读不到它们的 job 日志(403 Must have admin rights to Repository.),其 annotation 也不含测试名,因此在这个检出里无法指名是哪个套件。Release re-runs the unit suite main CI already ran: reuse a green CI verdict for the duplicated lane #10820 已经保存了该通道可解析日志的分析 —— 19 次 quality 失败运行中有 27 个不同的失败文件,只有 2 个重复,近期的是共享机器池上的 5000ms 超时 —— 其提出的补救办法(当同一 SHA 的主 CI 已经绿时跳过发布侧重复运行)位于发布 workflow 中。稳定版发布也刻意不带 VITEST_RETRY,所以一次抖动就会挡住发布。另外,以下各项同样未改动,且没有证据表明它们在本次运行中失败:十三个小的 workspace 配置(packages/channels/*integrations/*audio-capturechrome-extensionwebui)仍以 vitest 的 5 秒默认值运行、没有 ECS 上限;packages/acp-bridge/src/json-string-bytes.test.ts 中遍历 65536 个码元的用例在本地并行负载下需要 5.8 秒,会撞上那个 5 秒默认值;以及 scripts/tests/upload-aliyun-oss-assets.test.js 中与本次移除的七个同类的三个冗余 , 30_000) 上限。
  • 破坏性变更 / 迁移说明:无。本机上的 npm run test:scripts 会报告一个失败,scripts/tests/verify-capture.test.js > renders 256-colour and truecolor via the default-grey fallback,它是既有的且依赖环境:在基线配置(git show HEAD:scripts/tests/vitest.config.ts)和本分支配置下失败方式完全一致,约 200ms 就失败,是一个 PNG 像素颜色断言而不是超时。

关联 Issue

属于 #10853 的一部分 —— 本 PR 修复的是发布运行 33676423730 中失败的 quality_scripts 通道,也是那次运行中唯一公开具名了失败测试的通道。同一次运行中失败的三个 workspace 测试分片不在此处理,所以发布当时仍会因为它们而失败;该通道由 #10820 跟踪。刻意不使用自动关闭关键字,以便在分片通道也解决之前,这个发布失败 issue 保持开放。

…10853)

The v0.23.0 release run 33676423730 failed its quality gate on the
script-test lane: the deferred-findings upsert test in
qwen-autofix-workflow.test.js timed out at 30000ms. The test is healthy
— it replays the real upsert script against a recording gh stub sixty
times and needs 22.9s on an idle host — so ordinary contention on the
shared ecs-qwen pool, where the lane runs four vitest workers beside
the other release jobs, was enough to cross a flat 30s ceiling.

Every other suite already raises its ceiling on that pool (core, cli and
acp-bridge in #10552, vscode-ide-companion in #10672, web-shell in
#10765); scripts/tests was the one left flat, and it is the most
subprocess-heavy suite in the repo, run by both the release
quality_scripts lane and main CI's test:ci. Give it the same 60s ECS
branch, pin both branches in the config-parity suite, and drop the seven
per-test ", 30000)" ceilings that equalled the suite default and so
shadowed the clamp — a per-test ceiling outranks the config, verified
with a scratch probe, and the new pin is mutation-verified.

Non-ECS runners keep the 30s value byte for byte and no assertion
changes. The three workspace-test shards that failed in the same run are
not addressed: their logs are unreadable without admin rights and #10820
tracks that lane.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator Author

Autofix report — issue #10853 "Release Failed for v0.23.0 on 2026-09-02"

Branch autofix/issue-10853, one commit, three files changed (+42 / −11), all
under scripts/tests/. No production code and no assertion changed.

What the release run actually failed

The issue body names only quality, which is the aggregator. Per
GET /actions/runs/33676423730/jobs (tag v0.23.0, sha 8fde141a24 — the same
commit this checkout sits on):

Job ID Conclusion Failing step
Workspace Tests (1/3) 100411037278 failure 9 Run Workspace Tests
Workspace Tests (2/3) 100411037404 failure 9 Run Workspace Tests
Workspace Tests (3/3) 100411037364 failure 9 Run Workspace Tests
Quality Checks (Scripts) 100411037310 failure 9 Run Script Tests
Quality Checks 100421963904 failure 2 Verify quality results

quality_static, quality_build, quality_typecheck, both integration lanes
and the audio prebuilds all succeeded; Publish Release was skipped.

Job logs are not readable from this runner
(GET /actions/jobs/100411037310/logs403 Must have admin rights to Repository.), so the check-run annotations are the only public evidence. The
three workspace shards publish just Process completed with exit code 1. The
script lane published a named failure:

scripts/tests/qwen-autofix-workflow.test.js:13536
  > qwen-autofix workflow > upserts deferred findings into a per-PR issue that survives the merge
Error: Test timed out in 30000ms.

This round fixes that named failure. The workspace shards are not fixed here —
see "What this does not fix".

Root cause of the script-lane failure

The test is healthy, not hung. It replays the real
.github/scripts/upsert-deferred-issue.sh against a recording gh stub 60
times (runUpsert), each call a fresh temp dir plus a bash subprocess tree, and
it needs 22.9s on an idle host — 76% of the 30s ceiling it ran under:

✓ qwen-autofix workflow > upserts deferred findings into a per-PR issue that survives the merge  22945ms
Test Files  1 passed (1)   Tests  1 passed | 228 skipped (229)   Duration  32.17s

Every other suite in this repository already expects the shared self-hosted pool
to stretch a healthy test like that, and raises its ceiling when RUNNER_NAME
starts with ecs-qwen-:

Config Clamp Landed
packages/core, packages/cli 60s test/hook, 25% workers #8982, #10552
packages/acp-bridge 60s test/hook #10552
packages/vscode-ide-companion 60s test #10672
packages/web-shell 60s test/hook #10765 — merged 2026-09-02 04:58, ~15h before this release run
scripts/tests none — flat testTimeout: 30_000

The script suite is the one leg left unclamped, and it is the most
subprocess-heavy suite in the repo. The release lane runs it on ecs-qwen-hk4-10
with VITEST_MAX_THREADS=4 beside the other release jobs; main CI runs the same
suite inside npm run test:ci on the same pool.

Seven tests in qwen-autofix-workflow.test.js also carried their own
, 30000) ceiling — byte-identical to the suite default, so dead weight, and a
per-test ceiling outranks the config, so they would have shadowed the clamp
exactly where it was needed. One of them explained the removed convention in a
comment whose premise (a 5s suite default) had already gone stale.

Reproduction and mechanism evidence

  1. CI failure reproduced locally. With the 30s ceiling in force
    (RUNNER_NAME unset) and the host under contention from a concurrent full
    workspace-suite run (load ~150 on 64 cores), the same test fails at the same
    line with vitest's timeout error:

    ❯ scripts/tests/qwen-autofix-workflow.test.js:13536:5
    Test Files  1 failed (1)   Tests  1 failed | 228 skipped (229)
    
  2. The clamp is what changes the verdict. A throwaway probe file (created,
    run, deleted — never committed) with one test that sleeps 35s and one that
    sleeps 2s against an explicit , 1000) ceiling:

    Arm 35s test, no explicit ceiling 2s test, explicit , 1000)
    RUNNER_NAME unset (30s) failedTest timed out in 30000ms (30043ms) failed — Test timed out in 1000ms
    RUNNER_NAME=ecs-qwen-probe (60s) passed (35006ms) failed — Test timed out in 1000ms

    The first column shows the new branch raising the ceiling; the second shows a
    per-test ceiling outranking the config in both arms, which is why the seven
    redundant , 30000) arguments had to go rather than leaving the config alone.

  3. Witness + mutation probe. The new pin in
    scripts/tests/unit-vitest-configs.test.ts asserts both branches
    (ecs-qwen-parity → 60000, ubuntu-latest-runner → 30000). Flattening the
    ECS branch to 30_000 turns it red:

    × scripts suite timeout ceiling > keeps the shared-ECS ceiling in scripts/tests
      → RUNNER_NAME=ecs-qwen-parity: expected 30000 to be 60000 // Object.is equality
    Tests  1 failed | 26 passed (27)
    

    Restoring it: Tests 27 passed (27).

The change

  1. scripts/tests/vitest.config.tstestTimeout becomes 60s on ecs-qwen-*
    runners and stays 30s everywhere else, the same shape as the five clamped
    package configs. hookTimeout is deliberately not added: this suite has one
    beforeAll in total and no hook timeout was evidenced.
  2. scripts/tests/qwen-autofix-workflow.test.js — the seven , 30000)
    per-test ceilings removed (they equalled the suite default and shadowed the
    clamp); the deliberately tighter , 20000) / , 10000) ceilings left alone;
    the one comment that described the removed convention updated to point at the
    suite ceiling.
  3. scripts/tests/unit-vitest-configs.test.ts — the witness pin above.

What this does not fix

  • The three workspace-test shards. Their logs are unreadable from this
    runner and their annotations carry no test names, so no suite can be named.
    Release re-runs the unit suite main CI already ran: reuse a green CI verdict for the duplicated lane #10820 (maintainer-filed, open) has the parsable-log analysis of this exact
    lane: across 19 quality-failed runs there were 27 distinct failing test files
    with only 2 repeats, recent ones 5000ms timeouts on the shared pool, and the
    proposed remedy — skip the release re-run when main CI is already green for
    the SHA — lives in .github/workflows/release.yml, which the autofix
    boundaries keep out of autonomous reach. Stable releases also deliberately get
    no VITEST_RETRY, so a single flake blocks the release. Both are maintainer
    decisions, not something this round can settle.
  • The clamp is headroom, not immunity. Under the extreme synthetic load
    above (~8.5x slowdown) the same test ran 194s and would fail a 60s ceiling
    too. The fix moves headroom from 1.3x to 2.6x — the value maintainers already
    chose for cli, core, acp-bridge, web-shell and vscode-ide-companion.
  • Observed but left alone (no evidence they failed this run): thirteen small
    workspace configs (packages/channels/*, integrations/*,
    audio-capture, chrome-extension, webui) still have no ECS clamp and run
    at vitest's 5s default; packages/acp-bridge/src/json-string-bytes.test.ts's
    exhaustive 65536-code-unit case needed 5.8s under parallel load locally and
    timed out at the non-ECS 5s default (CI shelters it with acp-bridge's 60s
    clamp); scripts/tests/upload-aliyun-oss-assets.test.js carries three
    redundant , 30_000) ceilings of the same class as the seven removed here.
    Touching the workspace configs is the gate's test-config sensitive class, so
    they are named here instead of changed.

Verification

Commands actually run in this round, in order:

  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js -t "upserts deferred findings into a per-PR issue that survives the merge" — passed, 22945ms (healthy-runtime measurement).
  • Same command under contention with RUNNER_NAME unset — failed with vitest's timeout error at line 13536 (reproduces the CI failure).
  • Same command under contention with RUNNER_NAME=ecs-qwen-probe — failed too: this synthetic load (~8.5x slowdown, 194s test wall time) exceeds even the raised ceiling. Recorded as a limitation, not hidden.
  • Throwaway precedence probe (temp file, deleted before commit) — 35s test red at the 30s ceiling / green at the 60s ceiling; explicit , 1000) ceiling red in both arms.
  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/unit-vitest-configs.test.ts — 27 passed (witness green).
  • Mutation probe: ECS branch flattened to 30_000 → witness failed (expected 30000 to be 60000); restored → 27 passed.
  • npm run build — passed.
  • npm run typecheck — passed.
  • npm run lint — passed (no problems reported).
  • npm run test:scripts — 2095 passed | 16 skipped | 1 failed (2112 tests, 76 files). The single failure is scripts/tests/verify-capture.test.js > renders 256-colour and truecolor via the default-grey fallback (expected false to be true, a PNG pixel-colour assertion). Reproduced identically on the base config: git show HEAD:scripts/tests/vitest.config.ts written to a temp config and run against that file fails the same way, and with RUNNER_NAME unset this round's config change is a no-op (both branches yield 30_000). It is a font/rasterisation difference in this sandbox, not a regression from this change — see the base-probe output below.
  • npm run test:release:workspaces -- --passWithNoTests (diagnostic run of the failing release command at the release sha) — stopped inside packages/cli to stay inside the round's time budget. Completed workspaces: acp-bridge 1918 passed / 1 failed (the 5s-default timeout named above), audio-capture passed, chrome-bridge passed.
  • npx prettier --check on the three changed files — all formatted.

Base-probe output for the unrelated verify-capture failure:

=== BASE config (git show HEAD:scripts/tests/vitest.config.ts), RUNNER_NAME unset ===
   x verify-capture helper > renders 256-colour and truecolor via the default-grey fallback 250ms
   (the other 19 tests in the file pass)
BASE_EXIT=1

=== FIXED config (working tree), RUNNER_NAME unset ===
   x verify-capture helper > renders 256-colour and truecolor via the default-grey fallback 168ms
   (the other 19 tests in the file pass)
FIXED_EXIT=1

Identical failure on both configs, and it fails in ~200ms — an assertion about
rendered PNG pixels, not a timeout. Pre-existing in this sandbox.
中文说明

Autofix 报告 —— issue #10853「Release Failed for v0.23.0 on 2026-09-02」

分支 autofix/issue-10853,一个提交,改动三个文件(+42 / −11),全部位于
scripts/tests/ 之下。没有改动任何生产代码,也没有改动任何断言。

这次发布实际失败的是什么

issue 正文只写了 quality,而它只是聚合任务。根据
GET /actions/runs/33676423730/jobs(tag v0.23.0,sha 8fde141a24 —— 与本
检出所在的提交相同):

任务 ID 结论 失败步骤
Workspace Tests (1/3) 100411037278 failure 9 Run Workspace Tests
Workspace Tests (2/3) 100411037404 failure 9 Run Workspace Tests
Workspace Tests (3/3) 100411037364 failure 9 Run Workspace Tests
Quality Checks (Scripts) 100411037310 failure 9 Run Script Tests
Quality Checks 100421963904 failure 2 Verify quality results

quality_staticquality_buildquality_typecheck、两条集成测试通道以及
audio 预构建全部成功;Publish Release 被跳过。

这台 runner 读不到 job 日志
GET /actions/jobs/100411037310/logs403 Must have admin rights to Repository.),所以公开的 check-run annotation 是唯一的证据来源。三个
workspace 分片只给出 Process completed with exit code 1.,而脚本测试通道给出
了具名失败:

scripts/tests/qwen-autofix-workflow.test.js:13536
  > qwen-autofix workflow > upserts deferred findings into a per-PR issue that survives the merge
Error: Test timed out in 30000ms.

本轮修复的就是这个具名失败。workspace 分片不在本次修复范围内 —— 见「本次没有
修复的部分」。

脚本通道失败的根因

这个测试是健康的,并没有卡死。它用记录型 gh 桩真实回放了
.github/scripts/upsert-deferred-issue.sh 共 60 次(runUpsert),每次都要新建
临时目录并拉起一棵 bash 子进程树,在空载机器上需要 22.9 秒 —— 占它当时所处
30 秒上限的 76%:

✓ qwen-autofix workflow > upserts deferred findings into a per-PR issue that survives the merge  22945ms
Test Files  1 passed (1)   Tests  1 passed | 228 skipped (229)   Duration  32.17s

仓库里其他所有测试套件都已经认定共享自托管机器池会把这类健康测试拉长,并在
RUNNER_NAMEecs-qwen- 开头时抬高上限:

配置 上限处理 落地
packages/corepackages/cli test/hook 60s,workers 25% #8982#10552
packages/acp-bridge test/hook 60s #10552
packages/vscode-ide-companion test 60s #10672
packages/web-shell test/hook 60s #10765 —— 2026-09-02 04:58 合并,比本次发布早约 15 小时
scripts/tests 没有 —— 固定 testTimeout: 30_000

脚本套件是唯一没有加上限的一条,而它恰恰是仓库里子进程最密集的套件。发布通道在
ecs-qwen-hk4-10 上以 VITEST_MAX_THREADS=4 与其他发布任务并行运行它;主 CI 也
在同一个机器池里通过 npm run test:ci 运行同一个套件。

qwen-autofix-workflow.test.js 里还有七个测试各自带着 , 30000) 上限 —— 与套件
默认值逐字节相同,因此是无效负载;而单测试上限优先于配置,所以它们恰好会在最需要
的地方遮蔽新的上限。其中一个测试还用注释解释了这个被移除的约定,而该注释的前提
(套件默认 5 秒)早已过期。

复现与机制证据

  1. CI 失败在本地复现。 在 30 秒上限生效(RUNNER_NAME 未设置)且机器因并行
    运行完整 workspace 套件而处于争用状态(64 核上 load ~150)时,同一个测试在同一
    行以 vitest 的超时错误失败:

    ❯ scripts/tests/qwen-autofix-workflow.test.js:13536:5
    Test Files  1 failed (1)   Tests  1 failed | 228 skipped (229)
    
  2. 改变结论的正是这个上限。 一个临时探针文件(创建、运行、随后删除 —— 从未
    提交),其中一个测试 sleep 35 秒,另一个 sleep 2 秒并带显式 , 1000) 上限:

    分支 35 秒测试(无显式上限) 2 秒测试(显式 , 1000)
    RUNNER_NAME 未设置(30s) 失败 —— Test timed out in 30000ms(30043ms) 失败 —— Test timed out in 1000ms
    RUNNER_NAME=ecs-qwen-probe(60s) 通过(35006ms) 失败 —— Test timed out in 1000ms

    第一列说明新分支确实抬高了上限;第二列说明两种环境下显式单测试上限都优先于配
    置,这正是必须移除那七个冗余 , 30000) 参数、而不能只改配置的原因。

  3. 见证测试 + 变异探针。 scripts/tests/unit-vitest-configs.test.ts 中新增的
    见证断言覆盖两个分支(ecs-qwen-parity → 60000,ubuntu-latest-runner
    30000)。把 ECS 分支压平为 30_000 后它变红:

    × scripts suite timeout ceiling > keeps the shared-ECS ceiling in scripts/tests
      → RUNNER_NAME=ecs-qwen-parity: expected 30000 to be 60000 // Object.is equality
    Tests  1 failed | 26 passed (27)
    

    恢复后:Tests 27 passed (27)

改动内容

  1. scripts/tests/vitest.config.ts —— testTimeoutecs-qwen-* runner 上变为
    60 秒,其他环境保持 30 秒,与五个已加上限的包配置形状一致。刻意没有新增
    hookTimeout:整个套件只有一个 beforeAll,也没有任何 hook 超时的证据。
  2. scripts/tests/qwen-autofix-workflow.test.js —— 移除七个 , 30000) 单测试上限
    (它们与套件默认值相同并会遮蔽上限);刻意更紧的 , 20000) / , 10000) 保持
    不动;更新了那条描述被移除约定的注释,使其指向套件上限。
  3. scripts/tests/unit-vitest-configs.test.ts —— 上述见证断言。

本次没有修复的部分

  • 三个 workspace 测试分片。 这台 runner 读不到它们的日志,annotation 里也没有
    测试名,因此无法指名是哪个套件。Release re-runs the unit suite main CI already ran: reuse a green CI verdict for the duplicated lane #10820(维护者提交,仍开放)已经对这条通道做过
    可解析日志的分析:19 次 quality 失败的运行中共有 27 个不同的失败测试文件,只有
    2 个重复,近期的失败是共享机器池上的 5000ms 超时;其提出的补救办法 —— 当同一
    SHA 的主 CI 已经绿了就跳过发布侧重复运行 —— 位于
    .github/workflows/release.yml,该区域按 autofix 边界不允许自主改动。稳定版发布
    也刻意不带 VITEST_RETRY,所以一次抖动就会挡住发布。这两点都是维护者的决策,不
    是本轮能定的。
  • 上限是余量,不是免疫。 在上面那种极端合成负载下(约 8.5 倍减速),同一个测试
    跑了 194 秒,60 秒上限同样会失败。本次修复把余量从 1.3 倍提到 2.6 倍 —— 也就是
    维护者已经为 cli、core、acp-bridge、web-shell 和 vscode-ide-companion 选定的数值。
  • 观察到但未改动(没有证据表明它们在本次运行中失败): 十三个小的 workspace
    配置(packages/channels/*integrations/*audio-capture
    chrome-extensionwebui)仍然没有 ECS 上限,运行在 vitest 的 5 秒默认值上;
    packages/acp-bridge/src/json-string-bytes.test.ts 中遍历 65536 个码元的用例在本地
    并行负载下需要 5.8 秒,会在非 ECS 的 5 秒默认值上超时(CI 上被 acp-bridge 的 60
    秒上限保护);scripts/tests/upload-aliyun-oss-assets.test.js 还有三个与本次移除的
    七个同类的冗余 , 30_000) 上限。改动 workspace 配置属于门禁的 test-config 敏感
    类别,因此这里只点名、不改动。

验证

本轮实际执行的命令,按顺序:

  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js -t "upserts deferred findings into a per-PR issue that survives the merge" —— 通过,22945ms(健康耗时测量)。
  • 同一命令,在争用状态下且 RUNNER_NAME 未设置 —— 失败,在第 13536 行报 vitest 超时错误(复现 CI 失败)。
  • 同一命令,在争用状态下且 RUNNER_NAME=ecs-qwen-probe —— 同样失败:这种合成负载(约 8.5 倍减速,测试墙钟 194 秒)连抬高后的上限也超出。作为局限性如实记录,不做掩盖。
  • 临时优先级探针(临时文件,提交前已删除)—— 35 秒测试在 30 秒上限下红、在 60 秒上限下绿;显式 , 1000) 上限在两种环境下都红。
  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/unit-vitest-configs.test.ts —— 27 通过(见证为绿)。
  • 变异探针:把 ECS 分支压平为 30_000 → 见证失败expected 30000 to be 60000);恢复后 → 27 通过。
  • npm run build —— 通过。
  • npm run typecheck —— 通过。
  • npm run lint —— 通过(未报告任何问题)。
  • npm run test:scripts —— 2095 通过 | 16 跳过 | 1 失败(2112 个测试,76 个文件)。唯一失败是 scripts/tests/verify-capture.test.js > renders 256-colour and truecolor via the default-grey fallbackexpected false to be true,一个 PNG 像素颜色断言)。在基线配置上同样复现:把 git show HEAD:scripts/tests/vitest.config.ts 写成临时配置后运行该文件,失败方式完全一致;而且在 RUNNER_NAME 未设置时,本轮的配置改动本身就是空操作(两个分支都得到 30_000)。它是本沙箱中的字体/光栅化差异,不是本次改动引入的回归 —— 见下方基线探针输出。
  • npm run test:release:workspaces -- --passWithNoTests(在发布 sha 上对失败命令做的诊断运行)—— 为控制在本轮时间预算内,在 packages/cli 中途中止。已完成的 workspace:acp-bridge 1918 通过 / 1 失败(即上文提到的 5 秒默认值超时)、audio-capture 通过、chrome-bridge 通过。
  • 对三个改动文件执行 npx prettier --check —— 全部符合格式。

verify-capture 无关失败的基线探针输出:

=== BASE config (git show HEAD:scripts/tests/vitest.config.ts), RUNNER_NAME unset ===
   x verify-capture helper > renders 256-colour and truecolor via the default-grey fallback 250ms
   (the other 19 tests in the file pass)
BASE_EXIT=1

=== FIXED config (working tree), RUNNER_NAME unset ===
   x verify-capture helper > renders 256-colour and truecolor via the default-grey fallback 168ms
   (the other 19 tests in the file pass)
FIXED_EXIT=1

Identical failure on both configs, and it fails in ~200ms — an assertion about
rendered PNG pixels, not a timeout. Pre-existing in this sandbox.

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

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

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: observed, with solid evidence. Release run 33676423730 genuinely failed its quality gate (verified via the API: conclusion=failure), tracked in #10853, and the scripts lane named its failing test — Test timed out in 30000ms at ~22.9s of healthy runtime, i.e. a ceiling problem, not a hang. This is the suite-level companion to the per-test fixes already merged for the other workspaces.

Direction: aligned. scripts/tests/vitest.config.ts was the one suite left on a flat 30s while packages/core, packages/cli, packages/acp-bridge, packages/vscode-ide-companion, and packages/web-shell all already raise the ceiling when RUNNER_NAME starts with ecs-qwen- — this finishes that sweep for the most subprocess-heavy suite in the repo, which the release quality_scripts lane runs. No CHANGELOG reference needed; this is CI infrastructure.

Size: not applicable — no core paths touched; 53 lines, all test infrastructure (config + two test files), zero production code.

Approach: scope feels right. Three edits, each load-bearing: the ceiling clamp itself, removal of the seven per-test , 30000) ceilings that would have shadowed it (per-test ceilings outrank config — verified the deliberately tighter 20000/10000 ones are left alone), and a parity pin in unit-vitest-configs.test.ts holding both branches. The PR is honest about what it does not fix (the three workspace shards from the same release run are tracked in #10820) and deliberately omits the closing keyword so #10853 stays open — good scoping.

Risk: no elevated risk signals (no high-risk paths matched).

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:已观测到,证据充分。发布运行 33676423730 确实在 quality 门禁失败(已通过 API 核实:conclusion=failure),由 #10853 跟踪;脚本通道具名了失败测试——健康耗时约 22.9 秒却在 30 秒上限处报 Test timed out in 30000ms,是上限问题而非卡死。这是继其他 workspace 逐个修复之后,套件层面补上的最后一块。

方向:对齐。scripts/tests/vitest.config.ts 是唯一还停留在固定 30 秒的套件,而 packages/corepackages/clipackages/acp-bridgepackages/vscode-ide-companionpackages/web-shell 均已在 RUNNER_NAMEecs-qwen- 开头时抬高上限——本 PR 为仓库中子进程最密集的套件补齐了这一机制,发布的 quality_scripts 通道正是运行它的通道。无需 CHANGELOG 条目;这是 CI 基础设施。

规模:不适用——未触及核心路径;共 53 行,全部为测试基础设施(配置 + 两个测试文件),零生产代码。

方案:范围合理。三处改动各自必要:上限钳制本身、移除会遮蔽新上限的七个 , 30000) 单测试上限(单测试上限优先于配置——已核实刻意更紧的 20000/10000 未被触碰)、以及在 unit-vitest-configs.test.ts 中固定两个分支的断言。PR 诚实地说明了不修什么(同一次发布运行中失败的三个 workspace 分片由 #10820 跟踪),并刻意不加关闭关键字以保持 #10853 开放——范围把握得当。

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

进入代码审查 🔍

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code review

The diff does exactly three things, and each is load-bearing:

  1. scripts/tests/vitest.config.ts — the flat testTimeout: 30_000 becomes the same RUNNER_NAME?.startsWith('ecs-qwen-') ? 60_000 : 30_000 clamp that packages/core, packages/cli, and the other suites already use (verified: packages/core/vitest.config.ts uses the identical 60s ECS value). The non-ECS branch is byte-for-byte the old value, and the comment explains the why with the failure reference.
  2. scripts/tests/qwen-autofix-workflow.test.js — removes exactly the seven }, 30000); ceilings that duplicated the suite default. I verified against the base tree that these are all of them (the eighth grep hit is a 'setTimeout(() => {}, 30_000);' string fixture, correctly left alone), and the deliberately tighter 20000 / 10000 ceilings at lines 1543 / 11694 / 21601 are untouched. Removal is necessary, not cleanup: a per-test ceiling outranks the config, so any survivor would have shadowed the clamp exactly where it's needed.
  3. scripts/tests/unit-vitest-configs.test.ts — a parity pin asserting 60_000 under RUNNER_NAME=ecs-qwen-parity and 30_000 otherwise. It mirrors the existing bundle-guard timeout ceiling block above it line for line (stub → resetModules → dynamic re-import → assert → unstub), so the convention stays uniform.

No correctness, security, or convention issues found. One non-blocking note: the three redundant , 30_000) ceilings in scripts/tests/upload-aliyun-oss-assets.test.js (same class, would also shadow the new ceiling for those tests) are deliberately left out of scope per Risk & Scope — reasonable as a minimal fix tied to the observed failure, but worth a follow-up if that suite ever flakes on the pool.

Testing evidence — PR's own CI at the reviewed commit (this run never executes PR code)

The one red check is not this PR's: Dependency CVE audit failed on a uuid advisory (GHSA-w5hq-g745-h8pq, "4 vulnerabilities"), and the PR touches no dependency manifest or lockfile. The same workflow also fails on main at 8fde141 and 3ecc525 (earlier same-day runs were green) — pre-existing advisory noise, not a regression.

The main suite was still running at fetch time; per workflow rules this table is not polled here — the finalize job rewrites it once CI settles.

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

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

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

On the behavioural claim: the mechanism is pinned, not merely asserted — the new parity test runs in CI and fails if either branch of the clamp drifts, which is the strongest signal PR CI can give for a timeout-ceiling change. What no sandbox lane can reproduce is pool contention itself, so whether the headroom actually clears the release lane is observable only in the next release run; that is inherent to this kind of fix, not a gap a /verify or /tmux run could close. The author's 22.9s measurement and contended-host reproduction are their own reported numbers, cited as such; the part that matters for review — ceiling plumbing, not hang — is confirmed by the named CI failure at 30000ms.

中文说明

代码审查

diff 恰好做了三件事,且每件都必要:

  1. scripts/tests/vitest.config.ts——固定的 testTimeout: 30_000 变为与 packages/corepackages/cli 等套件相同的 RUNNER_NAME?.startsWith('ecs-qwen-') ? 60_000 : 30_000 钳制(已核实:packages/core/vitest.config.ts 使用完全相同的 60 秒 ECS 值)。非 ECS 分支与原值逐字节一致,注释说明了原因并附失败引用。
  2. scripts/tests/qwen-autofix-workflow.test.js——恰好移除七个与套件默认值重复的 }, 30000); 上限。已对照基线树核实这就是全部(第八处匹配是 'setTimeout(() => {}, 30_000);' 字符串 fixture,正确地未动),刻意更紧的 20000 / 10000 上限(1543 / 11694 / 21601 行)未触碰。移除是必要而非顺手清理:单测试上限优先于配置,任何幸存者都会恰好在最需要的地方遮蔽新上限。
  3. scripts/tests/unit-vitest-configs.test.ts——新增断言,固定 RUNNER_NAME=ecs-qwen-parity 下为 60_000、其余为 30_000。它与上方既有的 bundle-guard timeout ceiling 块逐行同构(stub → resetModules → 动态重导入 → 断言 → unstub),约定保持一致。

未发现正确性、安全性或约定问题。一条非阻塞提示:scripts/tests/upload-aliyun-oss-assets.test.js 中三个同类的冗余 , 30_000) 上限(同样会遮蔽新上限)按 Risk & Scope 刻意留在范围外——作为与观测失败绑定的最小修复是合理的,但若该套件未来在共享池上抖动,值得后续处理。

测试证据——被审提交自身的 CI(本审查不执行 PR 代码)

唯一的红色检查与本 PR 无关:Dependency CVE audituuid 公告(GHSA-w5hq-g745-h8pq)失败,而本 PR 未触碰任何依赖清单或锁文件。同一 workflow 在 main8fde1413ecc525 上同样失败(同日早些时候的运行还是绿的)——既有的公告噪音,不是回归。

抓取时主套件仍在运行;按工作流规则此处不轮询——finalize 任务会在 CI 落定后就地改写上表。

关于行为性主张:机制是被固定的,不只是被声称——新的奇偶测试在 CI 中运行,钳制的任一分支漂移都会使其失败,这是 PR CI 对超时上限类改动所能给出的最强信号。没有任何沙箱通道能复现机器池争用本身,因此余量是否真的让发布通道通过只能在下次发布运行中观察;这是此类修复的固有属性,不是 /verify/tmux 能补上的缺口。作者报告的 22.9 秒测量与争用机器复现是其自述数字,仅作引用;对审查真正重要的部分——是上限问题而非卡死——已由 CI 中具名的 30000ms 超时失败确认。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean, minimal, well-evidenced fix; the only nit is the deliberately deferred sibling ceilings in upload-aliyun-oss-assets.test.js.

This is what a good autofix looks like. The problem was observed and named — a real release run failed with a specific test at a specific ceiling — and the fix finishes an established pattern rather than inventing a new one: same clamp, same 60s value, same parity-suite pinning convention as the five suites that went before it. My independent take on how to fix this and the diff converged on the same three edits, and each edit is provably necessary — the seven removed ceilings would otherwise have shadowed the clamp, which the author clearly understood. The scoping is honest too: the workspace shards from the same release run are explicitly left to #10820, and the closing keyword is deliberately omitted so #10853 stays open. Six months from now this reads as routine maintenance, exactly right for the change.

Approving — but the PR's own CI (Qwen Code CI) was still in flight at review time, so approval is deferred until CI lands green on 3a8d30933bcf56c84a3d1bfa3f6226d83b43d7c9; the finalize step will post it automatically. The one red check (Dependency CVE audit) is pre-existing on main and untouched by this PR, so it should not gate.

中文说明

置信度:4/5 —— 干净、最小化、证据充分的修复;唯一的非阻塞点是刻意延后处理的 upload-aliyun-oss-assets.test.js 中的同类上限。

这是一次高质量的自动修复。问题是观测到且被具名的——真实的发布运行在特定测试的特定上限处失败——而修复方式是补全既有模式,而非发明新机制:与之前五个套件相同的钳制、相同的 60 秒值、相同的奇偶套件固定约定。我独立构想的修复方案与 diff 收敛于同样的三处改动,且每处改动都可证明是必要的——被移除的七个上限否则会遮蔽钳制,作者显然理解这一点。范围把握同样诚实:同一次发布运行中的 workspace 分片明确留给 #10820,并刻意省略关闭关键字以保持 #10853 开放。六个月后回看,这就是一次恰如其分的例行维护。

准备批准——但审查时 PR 自身的 CI(Qwen Code CI)仍在运行,因此批准推迟到该提交上 CI 全绿后由 finalize 步骤自动发布。唯一的红色检查(Dependency CVE audit)在 main 上既有且与本 PR 无关,不应成为门禁。

Qwen Code · qwen3.8-max

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed. Suggestions are inline.

Not explored to full depth (tool budget reached): "agent 1d": none — the walk finished inside budget..

中文说明

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

未探索到全部深度(达到工具调用预算):"agent 1d"none — the walk finished inside budget.

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

Comment thread scripts/tests/vitest.config.ts
Comment thread scripts/tests/qwen-autofix-workflow.test.js Outdated
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator Author

🔀 Base updated: red check(s) [Dependency CVE audit, Post Coverage Comment (ubuntu-latest, 22.x)] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [Dependency CVE audit, Post Coverage Comment (ubuntu-latest, 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed. Suggestions are inline.

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

  • rewritten-comment scope finding at scripts/tests/qwen-autofix-workflow.test.js:23766 — already reported as R1-2 (comment 3919789895), still standing at the reviewed commit

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

  • scripts/tests/unit-vitest-configs.test.ts:231 — [probe] D2-1 parity pin has no unset-RUNNER_NAME case; a '?'-removal mutation survives the pin and crashes local config loads
中文说明

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

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

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

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

Comment thread scripts/tests/vitest.config.ts
Comment thread scripts/tests/qwen-autofix-workflow.test.js Outdated
wenshao
wenshao previously approved these changes Sep 3, 2026
@qwen-code-dev-bot

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

Copy link
Copy Markdown
Collaborator Author

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

中文说明

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

@wenshao

wenshao commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Deep verification (local maintainer round, 2026-09-03) — verdict: merge-ready

94/94 scripted assertions passed, 0 unexpected failures, across six harnesses (ab-clamp 20, parity-mutations 22, variant-matrix 11, sync-block 8, static-claims 27, lane-assertions 6). Verified head db92f1b4215fb975895c8958aeaf24155fc5442b; merge ref ec2d9da8034a698c468a1f10587d3209d861f155 (HEAD^1 = base 055e831556ed…, HEAD^2 = PR head); effective diff = three files under scripts/tests/, lockfile untouched. Environment: credential-free node:22-bookworm container (Debian 12, aarch64, 2 CPU / 3.8 GiB) at the merge ref, depth 2, npm ci + npm run build, mirroring the release lane's prebuilt-dist shape. No blocking finding; three corrections to the description and five non-blocking observations below.

中文摘要

结论:merge-ready,94/94 条脚本化断言通过,0 个意外失败。

  • A/B(表 C1–C5,图 01)RUNNER_NAME=ecs-qwen-* 下 base 对 32s 探针判 Test timed out in 30000ms(即发布失败的机制),head 判通过(32034ms);非 ECS 分支两侧均仍为 30000ms,逐字节未变;带显式 , 30000) 的探针在 head 下仍判 30000ms —— 单测试上限确实优先于配置,七个删除是必要的。
  • 两半都承重(表 V1–V4,图 02):在发布通道真正失败的测试(base 13536–14756 行,自身带 }, 30000);)上跑四个组合:只加 clamp、只删上限都仍红在 30000ms,只有 PR 原样通过。
  • 新 pin 非空转(表 P0–P6,图 03):压平 60_000 / 改非 ECS 分支 / 改 runner 前缀均以预期 expected-vs-actual 失败;删 vi.resetModules() 也失败(承重);删 vi.unstubAllEnvs() 当前不可观测(防御性卫生,与相邻 pin 写法一致)。
  • 整条通道 A/B(图 06):head 2112/4 失败 vs base 2111/4 失败,四个失败两侧完全相同(容器 root 伪影),head 独有失败 0,用例差恰为新增 pin 且其通过。
  • 生产证据(图 05):失败 job 的 runner 为 ecs-qwen-hk4-10(clamp 前缀匹配该通道),VITEST_MAX_THREADS/FORKS: 8,具名测试 30801ms 死于 30000ms 上限。
  • Findings(均不阻塞):① 描述称 4 个 worker,真实为 8,争用比描述更严重(反而支持本 PR);② 三个 workspace 分片日志维护者可读且已指名,其失败均 testTimeout(ENOTEMPTY、expected false to be trueexpected 140.65 < 50Condition not met within 5000ms、spy 次数),抬高上限这类修复够不到它们,发布合并后仍会因它们失败;③ scripts/tests 只 clamp testTimeout 未 clamp hookTimeout(acp-bridge/web-shell 两者都 clamp),真实运行无 hook 超时,无证据表明有影响;④ upload-aliyun-oss-assets.test.js 三个同类 , 30_000) 仍会在池上遮蔽新上限(真实运行 23%/40%,PR 已声明);⑤ 新上限 60s 恰等于 test-setup.ts 记载的固定 60s worker→main RPC 期限;实测 45s 同步阻塞干净通过、65s 仍以具名 60000ms 干净失败(非“全绿但 exit 1”),故更糟模式在 65s 不成立,但纸面余量为零;⑥ 描述称“十三个”未 clamp 的小配置,普查为 16 个。
  • 未覆盖:非 ECS 分支的整条通道运行(值逐字节相同已静态证明 + 探针级 C3);真实池争用未复现(容器 2 CPU 空载);逐 commit 归因(4 commit = 1 实质 + 3 merge);vitest 的 60s RPC 常量引自仓库注释、未在发行 dist 定位;scripts/tests/*.ts 不在任何 typecheck 门禁内(既有属性);描述预测的 verify-capture 像素失败在本容器未复现(23/23 通过),取而代之的是四个 root 伪影;容器需装 jq(否则目标测试 0 退出且不调用 gh 桩,首轮矩阵作废)。

Central claim: the clamp fires on the pool, and only there

Probe = a scratch test in scripts/tests/ sleeping 32s (over the flat ceiling, under the raised one), driven through the real vitest with the real config. Oracle = vitest's verdict plus the ceiling it names in its timeout message (text reporter; the json reporter serialises timeouts as Error: STACK_TRACE_ERROR), corroborated by measured duration.

cell config RUNNER_NAME probe verdict named ceiling duration
C1 base unset inherits failed 30000ms 30039ms
C2 base ecs-qwen-probe inherits failed 30000ms 30036ms
C3 head unset inherits failed 30000ms 30032ms
C4 head ecs-qwen-probe inherits passed 32034ms
C5 head ecs-qwen-probe explicit , 30000) failed 30000ms 30029ms

C2 reproduces the release failure's mechanism; C4 is the fix; C3 is "non-ECS byte-for-byte unchanged"; C5 is why the seven removals are necessary.

A/B: base red at 30000ms on both arms, head green at 32034ms on ecs-qwen-*, non-ECS unchanged

The reported failure needs BOTH halves

The test the release lane named is itself one of the seven (base lines 13536–14756 close with }, 30000);). Its natural duration here is 9382ms, so every cell carries the same injected synchronous spawnSync('sleep', ['31']) at the top of its body; the cells then differ by nothing but the two hunks.

build verdict named ceiling duration
V1 base config + base test (what the release lane ran) failed 30000ms 40602ms
V2 clamp only, per-test ceiling left in place failed 30000ms 40723ms
V3 ceiling removed only, config still flat 30s failed 30000ms 40911ms
V4 head config + head test (the PR as shipped) passed 40508ms

Intermediate builds: clamp-only and ceiling-removal-only both stay red; only the PR as shipped passes

The new parity pin is not vacuous

Each cell mutates exactly one thing and first asserts the mutation landed. P0 = 27/27 in the file, matching the count the description claims; P1's failure message is byte-for-byte the one the description predicts.

cell mutation pin verdict message
P0 none (control) passed 27/27
P1 config ? 60_000? 30_000 failed RUNNER_NAME=ecs-qwen-parity: expected 30000 to be 60000
P2 config : 30_000,: 45_000, failed RUNNER_NAME=ubuntu-latest-runner: expected 45000 to be 30000
P3 prefix 'ecs-qwen-''ecs-qwenX-' failed expected 30000 to be 60000
P4 pin: drop vi.resetModules() failed expected 60000 to be 30000 — load-bearing
P5 none, ambient RUNNER_NAME=ecs-qwen-probe passed control on the other arm
P6 pin: drop vi.unstubAllEnvs() passed currently unobservable (last describe in the file)

Mutation matrix on the new parity pin

The 60s ceiling against the suite's own documented RPC deadline

scripts/tests/test-setup.ts documents a fixed 60s worker→main onTaskUpdate RPC timeout and warns that a single test stalling 60s still trips it, because testTimeout cannot interrupt synchronous bodies — and the test this PR fixes is exactly such a body. The new ceiling therefore equals that deadline, where base held half of it. Measured with a scratch probe blocking synchronously:

cell config block verdict duration named ceiling exit
S1 head 45s passed 45016ms 0
S2 head 65s failed 65024ms 60000ms 1
S3 base 45s failed 45019ms 30000ms 1

The scarier consequence does not hold at 65s — the failure stays a clean, attributed timeout, not the all-green exit-1 the comment warns about. What remains is a paper margin of zero between "admitted" and "RPC death" (observation 5).

Synchronous-block probe: 45s admitted cleanly, 65s still a clean named 60000ms timeout

Production witness and the lane-scale gate

From the failing job of release run 33676423730 (readable with maintainer access): Runner name: 'ecs-qwen-hk4-10' — the clamp's prefix matches the lane that failed — VITEST_MAX_THREADS/FORKS: 8, and the named test dying at 30801ms on a 30000ms ceiling.

Real release failure: runner ecs-qwen-hk4-10, 8 forks, 30801ms on a 30000ms ceiling

Whole-suite A/B, both cells with RUNNER_NAME=ecs-qwen-probe, differing only by the PR's three files: head 2112 tests / 4 failed / 2073 passed vs base 2111 / 4 / 2072. The four failures are byte-identical on both arms (root-privilege artifacts of the container: chmod-based unwritability does not apply to root); head-only failures 0; the collected-test delta is exactly the new pin, which passes at lane scale. The CI-named test passes at head here in 8848ms (15% of the new ceiling); the lane's slowest test is 12075ms (20%).

Whole-suite A/B: zero head-only failures, delta = the new pin

Margin off production data (526 test-level rows of the real run): the lane's slowest test was bite check: … at 25206ms — 84% of the old flat 30s ceiling, a second test one contention spike from the same failure, which the clamp also rescues; next 20625ms (69%). Of the seven de-ceilinged tests only the reported one was near the cap.

Corrections to the description (not to the code)

  1. Worker count. The lane ran with VITEST_MAX_THREADS/FORKS: 8 (the job's own env dump), not the "four vitest workers" the description states — vars.QWEN_CI_VITEST_MAX_WORKERS is 8, not the workflow's '4' fallback. Contention was worse than described, which strengthens the case; the 1.3×→2.6× margin table is about the ceiling and stands.
  2. The three workspace-shard logs are readable to a maintainer, and the failures can be named. None is a testTimeout: shard 1/3 session-pr-refresh.test.tsENOTEMPTY … rmdir; shard 2/3 local-bind-addresses.test.ts ×2 → expected false to be true, recall-scan-latency.test.tsexpected 140.65 to be less than 50 (in-test latency budget), MessageList.dom.test.tsx → spy called once not twice; shard 3/3 hook-runner.process.test.ts ×6 → five Condition not met within 5000ms (in-test polling budget) + one expected undefined to be defined. So the ceiling-raising class of fix cannot reach any of them — the PR's own "the release would still have failed on them" is confirmed, now with names and mechanisms.
  3. The sibling census says sixteen, not thirteen configs in the named families with no clamp and no testTimeout (channels 11, integrations 2, audio-capture, chrome-extension, webui's vite.config.ts). Direction right, number three short.

Findings (none blocking)

  1. hookTimeout is not clamped here while packages/acp-bridge and packages/web-shell clamp both; 11 files in the suite declare hooks; no hook timeout appears in the real run, so no evidence it bites today.
  2. Three same-class , 30_000) ceilings remain in scripts/tests/upload-aliyun-oss-assets.test.js (lines 182/224/249) and will keep shadowing the clamp on the pool; in the real run they took 23% and 40% of their ceiling, so the residual is modest and the PR names it.
  3. The admitted ceiling now equals the suite's documented 60s RPC deadline (see S1/S2): a margin observation, not a demonstrated hazard.
  4. The description's predicted pre-existing verify-capture pixel failure did not reproduce in this container (23/23 passed); four root-privilege artifacts failed instead, identically on both arms. Neither set is attributable to the PR.
  5. scripts/tests/*.ts is outside every typecheck gate (npm run typecheck is --workspaces --if-present), a pre-existing property of the suite; the pin was runtime-verified instead.

Not covered

Non-ECS arm at whole-lane scale (value proven byte-identical statically and at probe scale); real pool contention (this container is 2 CPU and idle; the injected delay stands in for it deterministically); per-commit attribution (4 commits = 1 substantive + 3 merges); the 60s RPC constant itself (quoted from the repo's setup comment, not pinned in vitest 3.2.7's dist); Windows/macOS lanes; #10820's shard-lane remedy. Environment repairs recorded because they invalidate first attempts: jq had to be installed (without it the target test exits 0 without invoking the recording gh stub, which made the first variant-matrix run red in all four cells for the wrong reason — discarded, logs kept), and a git identity was set for fixture repos.

Methodology, harnesses (ab-clamp.mjs, variant-matrix.mjs, parity-mutations.mjs, sync-block.mjs, static-claims.py, lane-gate.sh + compare-lanes.mjs + lane-assertions.mjs, ci-duration-census.py) and per-cell raw logs live in the round's artifact dir tmp/pr10858-verify-20260903-132201/ (report.md, verdict.txt, assertions.json, logs/, logs/cells/, evidence/).

…10853)

install-script.test.js carried a module-level vi.setConfig({ testTimeout:
30_000 }). A file-level override outranks the project config, so that suite
stayed on a flat 30s even on the ecs-qwen pool where the previous commit
raises the ceiling to 60s — the contention failure the PR exists to fix would
still have gone red on this leg. The override duplicated the old flat ceiling,
so it was inert until the config grew an ECS branch; dropping it restores the
ceiling and changes nothing off the shared pool, where the config still yields
30s byte for byte. The two deliberate describe-level timeouts are untouched.

Re-importing the config can never observe an override a suite applies to
itself at runtime, so pin its absence by source; mutation-verified by adding
the override back (pin red, names the file) and removing it (green). Also add
the unset-RUNNER_NAME arm the parity pin lacked: CI always sets RUNNER_NAME,
so with the override gone the pin was the only place that could witness the
config's optional chaining and it did not — deleting the `?` left the pin
green in CI while every local config load crashed. Mutation-verified both ways.

Finally scope a comment this PR rewrote: the file still keeps three deliberate
per-test budgets (20000/10000), so "like every other subprocess-spawning test
here" was false at file scope. Deleting the clause makes the sentence true
without hardcoding budgets 22k lines away from the comment that would describe
them.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator Author

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

Review round 3 — PR #10858 (issue #10853)

Two Suggestion-level findings, both reproduced before any edit, both resolved. One
reviewer-deferred item was also implemented because it closes a witness hole on a
branch this PR itself added. No --conflict, so no merge was performed.

Findings and dispositions

R1-1 — vi.setConfig({ testTimeout: 30_000 }) in install-script.test.js shadows the new ceiling — Implemented

([rc:3920870592], previously posted as [rc:3919789886])

Reproduced first with a two-arm scratch probe under RUNNER_NAME=ecs-qwen-parity
(config yields 60_000): the arm whose file carried vi.setConfig({ testTimeout: 1_000 })
failed with Error: Test timed out in 1000ms., while the identical test with the
override removed passed at 2004ms. The probe flips in both directions, so a
file-level override does outrank the project config. The premise also holds
historically: at the merge base the config read testTimeout: 30_000 flat, so the
override was an inert duplicate — this PR's ECS branch is what turned it into a live
shadow.

Fix is subtractive: the override is deleted. Off the shared pool nothing changes
(the config's else branch is the same 30_000); on ecs-qwen-* the suite now gets
the 60s the PR claims to give it. The two deliberate describe-level timeouts are
untouched and verified still present (Linux/macOS installer end-to-end { timeout: 15000 },
Windows installer end-to-end { timeout: 60_000 }), and vi remains heavily used
in the file so the import stays.

Also added the requested source pin in unit-vitest-configs.test.ts. The finding is
right that the existing config pin can never see this class of override — it
re-imports the config module, and the override lands later, at suite runtime. The
scan is flat because scripts/tests/ has no nested directories, so it matches the
config's own scripts/tests/**/*.test.{js,ts} include glob exactly.

R1-2 — rewritten comment claims a convention the file does not follow — Implemented, wording differs from the suggestion

([rc:3920870595], previously posted as [rc:3919789895])

Verified: qwen-autofix-workflow.test.js still carries }, 20000); at line 1543 and
}, 10000); at 11694 and 21601, all on subprocess-spawning tests, so "like every
other subprocess-spawning test here" was false at file scope.

Resolved by deleting the false clause rather than by adding the suggested one. The
suggested wording hardcodes (20000/10000) into a comment sitting ~22,000 lines away
from the budgets it describes, which creates a second thing to keep in sync — the
moment either budget changes, the comment is stale and the same class of drift the
finding complains about reappears. Dropping the universal claim makes the sentence
true on its own terms, is net −1 line, and leaves each budget explained where it
lives (line 1542 already carries its own "Spawn-heavy" rationale). The remaining
claim was checked too: the test really does run on the suite ceiling now, and
"Eight runGate arms" is accurate — exactly 8 runGate( call sites in that test body.

Reviewer-deferred D2-1 — parity pin has no unset-RUNNER_NAME case — Implemented

(rendered in [rv:5097575353], marked "recorded, not requested in this round")

Implemented anyway: it is one tuple entry, and it closes a witness hole on the
optional chaining this PR added. Reproduced the hole first — with ?. deleted from
scripts/tests/vitest.config.ts, the pin stayed green (28/28) under the CI
condition (RUNNER_NAME set), while a local load died with
TypeError: Cannot read properties of undefined (reading 'startsWith'). CI always
sets RUNNER_NAME, so the pin was the only place that could have caught it and did
not. After adding the unset arm the same mutation turns the pin red in CI too.

Left alone: the sibling bundle-guard timeout ceiling pin has the identical hole on
packages/vscode-ide-companion. That config is pre-existing and untouched by this
PR, so widening into it would be scope drift.

Changed files

File Change
scripts/tests/install-script.test.js −2: dropped the shadowing vi.setConfig override
scripts/tests/qwen-autofix-workflow.test.js −1: comment no longer claims a file-wide convention
scripts/tests/unit-vitest-configs.test.ts +26/−6: shadow-override pin, unset-RUNNER_NAME arm

Net +17 test lines against this window's 400-line test budget. No production source
changed, no test deleted or weakened, and all three files sit inside the PR's
existing scripts/tests/ footprint.

Failed checks on the PR

Test and web-shell E2E Smoke are CANCELLED and Post Coverage Comment is
FAILURE. Diagnosed from .github/workflows/ci.yml (read only — .github/ is out of
bounds to modify) as a cancellation cascade, not a code defect:

  • concurrency.cancel-in-progress is true for every ref except main and
    release/*, so each push to this branch cancels the run in flight. This branch
    carries three Merge branch 'main' commits, i.e. three superseding pushes.
  • post_coverage_comment has needs: [classify_pr, test], is
    continue-on-error: true by design, and its first substantive step downloads the
    coverage-reports-22.x-ubuntu-latest artifact that only the cancelled test job
    uploads — so a cancelled test makes it fail with nothing to consume.
  • That job reads only cli/coverage and core/coverage summaries. This PR touches
    zero files under packages/cli or packages/core, so it cannot affect its inputs.

No code change is indicated; this round's push starts a fresh run. This mode has no
GitHub credentials, so I could not read the CI logs — the above is a source-level
diagnosis of the workflow plus a locally green touched lane, not a log-confirmed one.

Local lane observation: a pre-existing flake, not fixed

npm run test:scripts failed on one test in two of three runs:
verify-capture.test.js > renders 256-colour and truecolor via the default-grey fallback. It is not caused by this PR and is deliberately not fixed here:

  • The file is byte-identical to base (git diff origin/main...HEAD -- scripts/tests/verify-capture.test.js → 0 lines); this PR touches only the three
    timeout-related files above.
  • It passes 23/23 in isolation on the final tree.
  • Run 1 (red) and run 2 (green, 76 files / 2097 passed / exit 0) used an identical
    tree and identical command
    , so the outcome is not a property of the diff.
  • A two-file pair run of verify-capture + qwen-autofix-workflow reproduces the
    failure — and that pair contains none of this round's functional changes, only the
    single comment line.
  • Mechanism: the assertion decodes a PNG and searches for an exact #d4d4d4 pixel in
    text that sharp/librsvg rasterises from an SVG requesting DejaVu Sans Mono. This
    sandbox has no fontconfig (fc-list unavailable, 0 fonts registered), so whether an
    exact-fill pixel survives anti-aliasing is unstable while the neighbouring suite
    spawns bash continuously for ~88s. The file has prior history of the same class:
    7918717ade fix(ci): avoid verify capture color conflict (#8236).

Fixing it would mean rewriting an unrelated test's pixel assertion, which the
reviewer never raised and which lies outside this PR's purpose. Flagging it instead.

Verification

Commands actually run this round:

  • npm run build — passed (exit 0); run twice, after the first three edits and again after the final edit
  • npm run typecheck — passed (exit 0); run twice
  • npm run lint — passed (exit 0); run twice
  • npx prettier --check on the four touched files — passed (exit 0); run twice
  • npx vitest run --config ./scripts/tests/vitest.config.ts unit-vitest-configs — 28 passed; also green with RUNNER_NAME set and unset
  • npx vitest run --config ./scripts/tests/vitest.config.ts verify-capture — 23 passed in isolation
  • npm run test:scripts (full scripts lane, 76 files) — run 1: 75 files passed | 1 failed (the verify-capture flake), exit 1. Run 2, identical tree: 76 files passed, 2097 passed | 16 skipped, exit 0. Run 3, final tree: 75 passed | 1 failed (same flake), exit 1. In all three runs every file this round touched passed: unit-vitest-configs 28/28, install-script 126 tests (16 skipped), qwen-autofix-workflow 229/229.
  • Pair run verify-capture qwen-autofix-workflow — 1 failed (the same flake) | 251 passed, exit 1; used to isolate the load mechanism

Mutation probes (each guard this round adds has its own witness):

  1. Shadow-override pin. Re-added vi.setConfig({ testTimeout: 30_000 }); to
    install-script.test.js → pin RED: expected [ 'install-script.test.js' ] to deeply equal []. Removed it again → GREEN 28/28. The green run also proves the
    pin does not match its own regex source.
  2. Unset-RUNNER_NAME arm. Deleted ?. from scripts/tests/vitest.config.ts
    pin GREEN 28/28 with RUNNER_NAME=fv-az123-ubuntu (hole confirmed) and
    failed to load config ... TypeError: Cannot read properties of undefined (reading 'startsWith') with RUNNER_NAME unset. Added the unset arm → same
    mutation now RED under the CI condition. Restored ?.GREEN under both
    conditions, and git diff scripts/tests/vitest.config.ts is empty, so no mutation
    leaked into the commit.
  3. Pre-fix reproduction of R1-1. Two scratch files under scripts/tests/, config
    ceiling 60_000 via RUNNER_NAME=ecs-qwen-parity: arm A carrying
    vi.setConfig({ testTimeout: 1_000 })Error: Test timed out in 1000ms.; arm B,
    identical test without it → passed at 2004ms. Both scratch files deleted
    afterwards; git status shows no untracked leftovers.

Not run, with reasons:

  • Integration tests after npm run bundle — the change is confined to vitest timeout
    configuration and test-suite source; nothing here is exercised through the bundled
    CLI or the integration harness.
  • npm run generate:settings-schema — no settings source changed.
中文说明

第 3 轮评审 — PR #10858(issue #10853

两条 Suggestion 级发现,均在动手修改前先复现,均已解决。另有一条被评审方标记为延后的项目也一并实现,因为它补上了本 PR 自身新增分支的见证漏洞。本轮 --conflict 为 false,因此未执行合并。

发现与处置

R1-1 — install-script.test.js 中的 vi.setConfig({ testTimeout: 30_000 }) 遮蔽了新上限 —— 已实现

[rc:3920870592],此前以 [rc:3919789886] 发布)

先用双臂临时探针在 RUNNER_NAME=ecs-qwen-parity(配置产出 60_000)下复现:文件携带 vi.setConfig({ testTimeout: 1_000 }) 的那一臂以 Error: Test timed out in 1000ms. 失败,而移除该覆盖后的同一测试在 2004ms 通过。探针双向翻转,说明文件级覆盖确实优先于项目配置。历史前提同样成立:在合并基点上配置是固定的 testTimeout: 30_000,因此该覆盖原本是冗余的重复——是本 PR 的 ECS 分支把它变成了有效遮蔽。

修复是减法:删除该覆盖。在共享池之外没有任何变化(配置的 else 分支同样是 30_000);在 ecs-qwen-* 上该套件现在获得 PR 声称给它的 60 秒。两个刻意的 describe 级超时未被触碰,并已确认仍然存在(Linux/macOS installer end-to-end{ timeout: 15000 }Windows installer end-to-end{ timeout: 60_000 });vi 在该文件中仍被大量使用,因此 import 保留。

同时按要求在 unit-vitest-configs.test.ts 中加了源码断言。该发现说得对:既有的配置断言永远看不到这一类覆盖——它重新导入配置模块,而覆盖发生在更晚的套件运行时。扫描是平铺的,因为 scripts/tests/ 没有嵌套目录,因此它与配置自身的 scripts/tests/**/*.test.{js,ts} include 通配完全一致。

R1-2 — 被重写的注释声称了一个该文件并不遵循的约定 —— 已实现,措辞与建议不同

[rc:3920870595],此前以 [rc:3919789895] 发布)

已核实:qwen-autofix-workflow.test.js 仍保留第 1543 行的 }, 20000); 以及第 11694、21601 行的 }, 10000);,三者都位于拉起子进程的测试上,因此 "like every other subprocess-spawning test here" 在文件范围内不成立。

处置方式是删除这个不成立的分句,而不是加上建议的分句。建议的措辞把 (20000/10000) 硬编码进一条距离它所描述的预算约 22000 行的注释里,这就多出了一处需要同步的内容——任一预算一旦变动,注释立即过期,而该发现所抱怨的同类漂移会再次出现。删掉这个全称声称后,句子本身就成立,净减 1 行,并让每个预算在它自己所在的位置被解释(第 1542 行本就带有自己的 "Spawn-heavy" 理由)。留下来的声称也核对过:该测试现在确实使用套件上限,而 "Eight runGate arms" 也准确——该测试体内恰好有 8 处 runGate( 调用。

评审方延后的 D2-1 — parity 断言缺少 RUNNER_NAME 未设置的分支 —— 已实现

(渲染于 [rv:5097575353],标注为"已记录,本轮不要求修改")

仍然实现:它只是一个元组条目,却补上了本 PR 新增的可选链的见证漏洞。先复现该漏洞——从 scripts/tests/vitest.config.ts 中删掉 ?. 后,在 CI 条件下(RUNNER_NAME 已设置)断言仍绿(28/28),而本地加载则以 TypeError: Cannot read properties of undefined (reading 'startsWith') 失败。CI 永远会设置 RUNNER_NAME,因此该断言本是唯一能捕获它的位置,却没有捕获。加上未设置分支后,同一变异在 CI 条件下也会让断言变红。

未处理:同级的 bundle-guard timeout ceiling 断言在 packages/vscode-ide-companion 上有完全相同的漏洞。该配置是既有代码且未被本 PR 触碰,因此扩展到它属于范围漂移。

变更文件

文件 变更
scripts/tests/install-script.test.js −2:删除遮蔽性的 vi.setConfig 覆盖
scripts/tests/qwen-autofix-workflow.test.js −1:注释不再声称文件级约定
scripts/tests/unit-vitest-configs.test.ts +26/−6:遮蔽覆盖断言、RUNNER_NAME 未设置分支

净增 17 行测试代码,本窗口测试预算为 400 行。未改动任何生产源码,未删除或削弱任何测试,三个文件都在本 PR 既有的 scripts/tests/ 足迹内。

PR 上的失败检查

Testweb-shell E2E Smoke 为 CANCELLED,Post Coverage Comment 为 FAILURE。依据 .github/workflows/ci.yml(只读——.github/ 属于禁止修改范围)诊断为取消级联,而非代码缺陷:

  • concurrency.cancel-in-progress 对除 mainrelease/* 之外的所有 ref 都为 true,因此向本分支的每次推送都会取消正在进行的那次运行。本分支带有三个 Merge branch 'main' 提交,即三次相互取代的推送。
  • post_coverage_commentneeds[classify_pr, test],设计上带 continue-on-error: true,而其第一个实质步骤要下载只有被取消的 test 作业才会上传的 coverage-reports-22.x-ubuntu-latest 产物——因此 test 被取消会让它在无产物可用的情况下失败。
  • 该作业只读取 cli/coveragecore/coverage 摘要。本 PR 在 packages/clipackages/core 下改动了零个文件,因此不可能影响它的输入。

无需代码改动;本轮推送会启动一次全新运行。本模式没有 GitHub 凭据,因此我无法读取 CI 日志——以上是对 workflow 的源码级诊断加上本地已绿的受影响通道,并非经日志确认的结论。

本地通道观察:一个既有 flake,未修复

npm run test:scripts 在三次运行中的两次里有一个测试失败:verify-capture.test.js > renders 256-colour and truecolor via the default-grey fallback。它不是本 PR 造成的,并且这里刻意不修复:

  • 该文件与基点逐字节相同(git diff origin/main...HEAD -- scripts/tests/verify-capture.test.js → 0 行);本 PR 只改动了上述三个与超时相关的文件。
  • 在最终代码树上单独运行通过 23/23。
  • 第 1 次运行(红)与第 2 次运行(绿,76 文件 / 2097 通过 / exit 0)使用完全相同的代码树与命令,因此结果不是 diff 的属性。
  • 只运行 verify-capture + qwen-autofix-workflow 两个文件的配对运行能复现该失败——而这个组合不包含本轮任何功能性改动,只有那一行注释。
  • 机制:该断言解码一张 PNG,并在 sharp/librsvg 从一个请求 DejaVu Sans Mono 的 SVG 光栅化出的文本中查找精确的 #d4d4d4 像素。本沙箱没有 fontconfig(fc-list 不可用,注册字体数为 0),因此在相邻套件持续约 88 秒不断拉起 bash 的同时,抗锯齿后是否还留下精确填充色的像素并不稳定。该文件此前就有同类历史:7918717ade fix(ci): avoid verify capture color conflict (#8236)

修复它意味着改写一个无关测试的像素断言,而评审方从未提出这一点,且它超出本 PR 的目的。因此改为在此标记。

验证

本轮实际运行的命令:

  • npm run build — 通过(exit 0);运行两次,分别在前三处编辑之后与最后一处编辑之后
  • npm run typecheck — 通过(exit 0);运行两次
  • npm run lint — 通过(exit 0);运行两次
  • 对四个受影响文件执行 npx prettier --check — 通过(exit 0);运行两次
  • npx vitest run --config ./scripts/tests/vitest.config.ts unit-vitest-configs — 28 通过;在 RUNNER_NAME 已设置与未设置两种情况下均为绿
  • npx vitest run --config ./scripts/tests/vitest.config.ts verify-capture — 单独运行 23 通过
  • npm run test:scripts(完整 scripts 通道,76 个文件)——第 1 次:75 文件通过 | 1 失败(verify-capture flake),exit 1。第 2 次,代码树相同:76 文件通过,2097 通过 | 16 跳过,exit 0。第 3 次,最终代码树:75 通过 | 1 失败(同一 flake),exit 1。三次运行中本轮触碰的每个文件都通过:unit-vitest-configs 28/28、install-script 126 个测试(16 跳过)、qwen-autofix-workflow 229/229。
  • 配对运行 verify-capture qwen-autofix-workflow — 1 失败(同一 flake)| 251 通过,exit 1;用于隔离出负载机制

变异探针(本轮新增的每个守卫都有自己的见证):

  1. 遮蔽覆盖断言。install-script.test.js 重新加回 vi.setConfig({ testTimeout: 30_000 }); → 断言expected [ 'install-script.test.js' ] to deeply equal []。再次移除 → 绿 28/28。这次绿色运行同时证明该断言不会匹配到自己的正则源码。
  2. RUNNER_NAME 未设置分支。scripts/tests/vitest.config.ts 删除 ?. → 在 RUNNER_NAME=fv-az123-ubuntu 下断言绿 28/28(漏洞确认),在 RUNNER_NAME 未设置时 failed to load config ... TypeError: Cannot read properties of undefined (reading 'startsWith')。加上未设置分支后 → 同一变异在 CI 条件下变为。恢复 ?. → 两种条件下均绿,且 git diff scripts/tests/vitest.config.ts 为空,说明没有变异泄漏进提交。
  3. R1-1 的修复前复现。scripts/tests/ 下建两个临时文件,通过 RUNNER_NAME=ecs-qwen-parity 使配置上限为 60_000:携带 vi.setConfig({ testTimeout: 1_000 }) 的 A 臂 → Error: Test timed out in 1000ms.;不带该覆盖的相同测试 B 臂 → 在 2004ms 通过。两个临时文件随后删除;git status 显示没有残留的未跟踪文件。

未运行及原因:

  • npm run bundle 之后的集成测试——改动仅限于 vitest 超时配置与测试套件源码;这里没有任何内容通过打包后的 CLI 或集成测试框架被执行。
  • npm run generate:settings-schema——没有改动任何 settings 源。

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

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

Re-review when you have a moment. After round 10 this bot stops and leaves the PR for a human. · 有空请复审;第 10 轮后本 bot 停止并将 PR 交给人工。


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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed. Suggestions are inline.

中文说明

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

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

Comment thread scripts/tests/unit-vitest-configs.test.ts Outdated
…errides (#10853)

The anti-shadowing pin added last round policed a narrower surface than the one
it exists to protect. Its regex matched testTimeout only as the first key of a
vi.setConfig({...}) call, its walk of scripts/tests was non-recursive while the
suite's include glob is recursive, and its \.test\.[jt]s$ filter excluded
test-setup.ts — the config's sole setupFiles entry, where an override lands
after project-config resolution and outranks the ceiling exactly like the
install-script.test.js override this PR removes. Any one of the three lets a
flake-fix re-pin the suite at the old flat 30s on the shared ecs-qwen pool
while the pin reads green.

Widen the walk to the config's include+setup surface: recursive, every .js/.ts
under scripts/tests, and a pattern that no longer cares where in the object the
key sits. vitest.config.ts writes testTimeout inside a config object rather
than through vi.setConfig(, so the wider filter stays false-positive-free. Read
through join() because a recursive readdirSync returns nested entries carrying
the platform separator, which new URL(name, here) does not accept.

Mutation-verified three ways: vi.setConfig({ testTimeout: 1 }) in test-setup.ts
fails the pin naming that file and passes again once reverted; a multi-key
vi.setConfig({ hookTimeout, testTimeout }) and a nested
scripts/tests/<sub>/*.test.js override both read green under the old logic and
are flagged under the new one.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator Author

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

Round 4 — PR #10858 (issue #10853)

One commit this round: b097f06f05, one file, +9/−6, entirely inside the PR's existing footprint.

Feedback dispositions

[rc:3922637899] R3-1 (Suggestion) — IMPLEMENTED, resolved in code

The claim is that the anti-shadowing pin added last round polices a narrower surface than the shadowing surface it exists to catch. All three parts verified against the exact reviewed commit (3941251556) before changing anything:

  • the pattern /vi\.setConfig\(\s*\{\s*testTimeout/ requires testTimeout to be the first key of the object;
  • readdirSync(fileURLToPath(here)) walks scripts/tests non-recursively, while the suite's include glob scripts/tests/**/*.test.{js,ts} is recursive;
  • the /\.test\.[jt]s$/ filter excludes scripts/tests/test-setup.ts — measured: false for that name — which is the config's sole setupFiles entry, where an override lands after project-config resolution and outranks the ceiling exactly like the install-script.test.js override this PR removes.

Fix, in scripts/tests/unit-vitest-configs.test.ts only:

  • readdirSync(here, { encoding: 'utf8', recursive: true }) — the explicit encoding selects the string[] overload in the installed @types/node, so no String() coercion is needed;
  • file filter widened to /\.[jt]s$/, bringing the setup file inside the scan;
  • pattern widened to /vi\.setConfig\([^)]*\btestTimeout\b/, so key position stops mattering while [^)]* keeps the match inside one call;
  • reads through join(here, name) because a recursive readdirSync returns nested entries carrying the platform separator, which new URL(name, here) does not accept.

False-positive check: vitest.config.ts writes testTimeout inside a config object, never through vi.setConfig(, and nothing anywhere under scripts/tests contains vi.setConfig at all — so the widened walk flags nothing and the file stays 28/28 green.

[rv:5099647405] review body — NO ACTION

"Reviewed. Suggestions are inline." It carries no claim of its own; it is the cover for the inline finding above.

[ic:5521567151] @wenshao deep verification (verdict: merge-ready) — acknowledged, no code change

This is a verification report rather than a change request, and nothing in it asks for a diff. Dispositions on its contents:

Description corrections (worker count is 8 not 4; the three workspace-shard failures are named and none is a testTimeout; the sibling census is 16 not 13): acknowledged as correct. They fix PR prose, not code, and this round has no mechanism to rewrite the PR description — recorded here so they are not lost at merge.

Finding 1 — hookTimeout is not clamped in scripts/tests while sibling configs clamp it: DECLINED. The gap is real and slightly broader than reported: packages/core, packages/cli, packages/acp-bridge and packages/web-shell all carry an ECS hookTimeout branch, and scripts/tests/vitest.config.ts carries none. But there is no witness — no hook timeout in the production run the PR fixes, none in a full local suite run (2097 passed / 16 skipped / 0 failed) — and the issue's own evidence is a testTimeout death at 30801ms on a 30000ms ceiling. Under this repo's mutation-witness rule an added clamp would also need its own parity pin, so the true cost is roughly eight lines across two files for a hazard nobody has observed, against AGENTS.md Simplicity First. It stays a one-line follow-up if a hook ever times out on the pool.

Finding 2 — three , 30_000) ceilings remain at upload-aliyun-oss-assets.test.js:182,224,249: DECLINED. Confirmed at exactly those lines. This is a residual the PR already discloses rather than an oversight, the same report measures those tests at 23% and 40% of their ceiling in the real run, and it is marked non-blocking on a merge-ready verdict. Re-opening a disclosed scope boundary in a later round grows the diff with no witness that any of the three fails. The widened pin deliberately polices vi.setConfig — a silent file-wide override — and not a deliberate single-test budget, so it neither flags nor requires those three.

Finding 3 — the new 60s ceiling equals the suite's documented 60s worker→main RPC deadline: acknowledged, no action. The same report's S1/S2 probes show the scarier all-green-exit-1 mode does not hold at 65s (a clean, named 60000ms timeout instead). Trading the demonstrated #10853 failure for a paper margin would be the wrong exchange.

Finding 4 — the predicted verify-capture pixel failure did not reproduce in their container: acknowledged. It did reproduce here on one run and not on the next (see Failed checks), which is consistent with an environment-sensitive test this PR never touches.

Finding 5 — scripts/tests/*.ts sits outside every typecheck gate: acknowledged as pre-existing. Compensated this round with a targeted standalone tsc run on the changed file: 22 errors, all pre-existing TS2322 on the untouched configs registry, identical in kind and count when the same command runs against the base version of the file. So the change adds no new type error even though no gate would have caught one.

Failed checks

Test (ubuntu-latest, Node 22.x) FAILURE, Post Coverage Comment FAILURE, Lint & Static CANCELLED, web-shell E2E Smoke CANCELLED.

I could not read the job logs — this round holds no GitHub credentials — so I reproduced locally and traced what the run actually executed.

What the diff can and cannot reach. The PR's whole footprint is four files under scripts/tests/. In the ci.yml the run executed, the Test job's step ran npm run test:ci:workspaces and then, only on success, npm run test:scripts — so nothing in this diff can touch a workspace suite. And no budget the diff touches is lowered anywhere: base carried a flat testTimeout: 30_000 plus seven explicit }, 30000); and one vi.setConfig({ testTimeout: 30_000 }); head yields 30_000 off-pool (byte-identical) and 60_000 on ecs-qwen-*, with every explicit ceiling removed.

Reproducing the #10853 failure on this host. Full scripts suite, same tree, same host, two arms:

arm RUNNER_NAME ceiling …stale-duplicate revalidation… suite
off-pool unset 30s (= base, byte-identical) × 39821ms — Test timed out in 30000ms. 73/76 files; 2094 passed / 3 failed / 16 skipped
on-pool ecs-qwen-probe 60s (the PR's fix) ✓ 33282ms 76/76 files; 2097 passed / 16 skipped / 0 failed; exit 0

That test genuinely needs 33.3s under whole-suite contention — over the old flat 30s, under the new 60s — and it passes in 13329ms in isolation. That is the #10853 mechanism and its fix, reproduced end to end. Base carried }, 30000); on this same test (base line 2076), so the off-pool arm's 30s budget is byte-identical before and after this PR: the timeout is not a regression the diff introduced.

The other two off-pool failures are environmental, and both cleared on the on-pool run:

  • install-script.test.js > does not package audio-capture test artifactsENOENT: stat packages/audio-capture/dist. That directory did not exist in this checkout; it does after npm run build, and the test then passes in 1027ms. A missing build artifact, not a timeout.
  • verify-capture.test.js > renders 256-colour and truecolor via the default-grey fallbackexpected false to be true, in a file this PR never touches, and it passed (2983ms) on the second run — matching finding 4's non-reproduction.

The run's own shape points at the pool, not the code. web-shell E2E Smoke was cancelled at 20.9 minutes against its timeout-minutes: 20. Main had also restructured these jobs the same morning — b8f35aa5d3 split lint and static checks out of the Test job at 03:53, 035418958d added the contended-attempt retry at 03:55 — so the Test job that ran at 07:08 was main's new one, not the shape this branch last saw green. Post Coverage Comment failed 13 seconds after starting, consistent with a Test job that never reached its coverage upload.

Conclusion. No evidence ties the Test failure to this diff, and the diff cannot lower any timeout, so there is nothing in it to fix. I am not claiming the check is fixed — the workflow's independent CI re-run is the gate that settles it. If it goes red again, the table above says where to look first.

Changes

One commit, b097f06f05, one file, +9/−6:

  • scripts/tests/unit-vitest-configs.test.ts — widened the anti-shadowing walk to the suite's include+setup surface (R3-1).

The PR footprint stays at four files under scripts/tests/. No area outside the PR's own footprint was touched, and no CI or verification machinery was modified. Net diff growth this round is +3 test lines against a 400-line budget.

Conflict notes

--conflict false, so no merge of origin/main was performed. Worth flagging to the maintainer: origin/main has moved well past this branch's merge base (055e831556a8248eaed3, 14 commits, three of which restructured ci.yml). There is no conflict because the PR touches no workflow file, but the branch has not been CI-green against current main.

Verification

  • npm run build — passed (exit 0)
  • npm run typecheck — passed (exit 0)
  • npm run lint — passed (exit 0)
  • npx eslint scripts/tests/unit-vitest-configs.test.ts — passed, no findings
  • npx prettier --check scripts/tests/unit-vitest-configs.test.ts — passed, "All matched files use Prettier code style!"
  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/unit-vitest-configs.test.ts — 28 passed (28), 1 file passed; re-run green after every probe revert
  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js -t 'behaviorally replays the stale-duplicate revalidation' — 1 passed in 13329ms (isolation baseline)
  • Full scripts suite, off-pool arm (RUNNER_NAME unset) — 73/76 files; 2094 passed / 3 failed / 16 skipped; all three failures traced above
  • Full scripts suite, on-pool arm (RUNNER_NAME=ecs-qwen-probe), after npm run build76/76 files; 2097 passed / 16 skipped / 0 failed; exit 0
  • Targeted npx tsc --noEmit --strict on the changed file — 22 errors, all pre-existing TS2322; the identical command on the base version of the same file yields the same 22, so the change adds none
  • No integration run and no npm run bundle: the change is confined to a static-analysis pin under scripts/tests, which no bundled-CLI or integration path exercises
  • No npm run generate:settings-schema: no settings source changed

Mutation probes — each planted, run, then reverted, with git status --short confirmed clean afterwards:

probe planted pre-fix logic post-fix guard
A vi.setConfig({ testTimeout: 1 }) appended to scripts/tests/test-setup.ts never scanned — filter measured false on that name RED: expected [ 'test-setup.ts' ] to deeply equal []; green again after revert
B vi.setConfig({ hookTimeout: 15_000, testTimeout: 30_000 }) in scripts/tests/zz-probe-multikey.test.js GREEN — the hole: flags [] RED: flags the file
C vi.setConfig({ testTimeout: 30_000 }) in scripts/tests/zz-probe-nested/zz-probe-nested.test.js GREEN — the hole: the non-recursive walk never saw it RED: flags zz-probe-nested/zz-probe-nested.test.js
中文说明

第 4 轮 —— PR #10858(issue #10853

本轮一个提交:b097f06f05,一个文件,+9/−6,完全落在 PR 既有的改动范围内。

反馈处置

[rc:3922637899] R3-1(Suggestion)—— 已实现,代码中已解决

该发现指出:上一轮新增的防遮蔽 pin,其防守面比它要抓的遮蔽面更窄。三条主张都在动手之前对照被审查的那个提交(3941251556)逐一核实:

  • 正则 /vi\.setConfig\(\s*\{\s*testTimeout/ 要求 testTimeout 必须是对象的第一个键;
  • readdirSync(fileURLToPath(here))scripts/tests 的遍历是非递归的,而套件的 include glob scripts/tests/**/*.test.{js,ts} 是递归的;
  • /\.test\.[jt]s$/ 过滤把 scripts/tests/test-setup.ts 排除在外 —— 实测该文件名匹配结果为 false —— 而它正是配置里唯一的 setupFiles 条目,在那里写入的覆盖同样在项目配置解析之后生效、并且优先于配置上限,与本轮 PR 刚删掉的 install-script.test.js 覆盖完全同类。

修复仅涉及 scripts/tests/unit-vitest-configs.test.ts

  • readdirSync(here, { encoding: 'utf8', recursive: true }) —— 显式写出 encoding 会命中已安装 @types/node 中返回 string[] 的那个重载,因此不需要 String() 转换;
  • 文件过滤放宽为 /\.[jt]s$/,把 setup 文件纳入扫描面;
  • 正则放宽为 /vi\.setConfig\([^)]*\btestTimeout\b/,使键的位置不再重要,同时 [^)]* 保证匹配不会跨出单次调用;
  • 文件读取改用 join(here, name),因为递归 readdirSync 返回的嵌套条目带平台路径分隔符,而 new URL(name, here) 不接受它。

误报核查:vitest.config.ts 是在配置对象里写 testTimeout,从不通过 vi.setConfig(;而 scripts/tests 下任何文件都不含 vi.setConfig —— 所以放宽后的遍历不会标出任何东西,该文件仍是 28/28 全绿。

[rv:5099647405] review 正文 —— 无需处理

"Reviewed. Suggestions are inline."(已审查,建议见行内评论。)它本身不承载任何主张,只是上面那条行内发现的封面。

[ic:5521567151] @wenshao 深度验证(结论:merge-ready)—— 已确认,不改代码

这是一份验证报告而非修改请求,其中没有任何内容要求产生 diff。对其内容的处置如下:

对描述的更正(worker 数是 8 不是 4;三个 workspace 分片的失败已被指名且无一testTimeout;同类配置普查是 16 个不是 13 个):确认无误。它们更正的是 PR 文案而非代码,而本轮没有改写 PR 描述的途径 —— 在此记录,以免合并时丢失。

发现 1 —— scripts/tests 未 clamp hookTimeout,而同族配置都 clamp 了:已拒绝(Decline)。 该缺口真实存在,且比报告所述更宽:packages/corepackages/clipackages/acp-bridgepackages/web-shell 四个配置都带 ECS 的 hookTimeout 分支,而 scripts/tests/vitest.config.ts 一个都没有。但没有任何证据支撑 —— 本 PR 要修的那次生产运行里没有出现 hook 超时,本地整套跑完(2097 通过 / 16 跳过 / 0 失败)也没有 —— 而 issue 自身的证据是一次 testTimeout 死亡:30801ms 撞在 30000ms 上限上。按本仓库的变异见证规则,新增的 clamp 还需要它自己的 parity pin,所以真实成本是两个文件约八行代码,去防一个无人观测到的风险,这与 AGENTS.md 的 Simplicity First 相悖。如果哪天 hook 真的在该机器池上超时,它仍是一行代码的后续项。

发现 2 —— upload-aliyun-oss-assets.test.js:182,224,249 仍留有三个 , 30_000) 上限:已拒绝(Decline)。 已确认正是这三行。这是 PR 已经主动披露的残留,而非疏漏;同一份报告测得它们在真实运行中只用了各自上限的 23% 和 40%;并且在 merge-ready 结论下被标为非阻塞。在后续轮次里重新打开一个已披露的范围边界,只会让 diff 变大,而没有任何证据表明这三者中有哪一个会失败。放宽后的 pin 有意只防守 vi.setConfig(一种静默的、影响整个文件的覆盖),而不防守有意写下的单测试预算,所以它既不会标出也不要求处理这三处。

发现 3 —— 新的 60s 上限恰好等于套件自己记载的 60s worker→main RPC 期限:已确认,不做处理。 同一份报告的 S1/S2 探针表明,更可怕的那种"全绿但 exit 1"模式在 65s 并不成立(取而代之的是一次干净、具名的 60000ms 超时)。用已证实的 #10853 失败去换一点纸面余量,是划不来的交换。

发现 4 —— 描述预测的 verify-capture 像素失败在他们的容器里没有复现:已确认。 它在本机的一次运行中复现了、下一次又没有(见下方"失败的检查"),这与一个本 PR 从未触碰、且对环境敏感的测试的表现一致。

发现 5 —— scripts/tests/*.ts 不在任何 typecheck 门禁内:确认为既有属性。 本轮以针对性的独立 tsc 运行作了补偿:改动文件上有 22 个错误,全部是未触碰的 configs 注册表上的既有 TS2322;对同一文件的 base 版本运行完全相同的命令,得到的错误在种类和数量上完全一致。因此即使没有门禁能抓到,本次改动也确实没有新增任何类型错误。

失败的检查

Test (ubuntu-latest, Node 22.x) FAILURE、Post Coverage Comment FAILURE、Lint & Static CANCELLED、web-shell E2E Smoke CANCELLED。

我无法读取 job 日志 —— 本轮不持有任何 GitHub 凭据 —— 所以改为本地复现,并追溯那次运行实际执行了什么。

这个 diff 能碰到什么、碰不到什么。 PR 的全部改动范围是 scripts/tests/ 下的四个文件。在那次运行实际执行的 ci.yml 里,Test job 的那一步先跑 npm run test:ci:workspaces,只有成功后才跑 npm run test:scripts —— 所以本 diff 中的任何内容都碰不到 workspace 套件。而且 diff 涉及的预算没有任何一处被调低:base 是一个固定的 testTimeout: 30_000,外加七个显式 }, 30000); 和一个 vi.setConfig({ testTimeout: 30_000 });head 在非池上产出 30_000(逐字节相同),在 ecs-qwen-* 上产出 60_000,并删除了全部显式上限。

在本机复现 #10853 的失败。 完整 scripts 套件,同一棵树、同一台主机、两个分支:

分支 RUNNER_NAME 上限 …stale-duplicate revalidation… 套件
非池 未设置 30s(= base,逐字节相同) × 39821ms —— Test timed out in 30000ms. 73/76 文件;2094 通过 / 3 失败 / 16 跳过
池上 ecs-qwen-probe 60s(即本 PR 的修复) ✓ 33282ms 76/76 文件;2097 通过 / 16 跳过 / 0 失败;exit 0

该测试在整套并发争用下确实需要 33.3 秒 —— 超过旧的固定 30s,落在新的 60s 之内 —— 而单独跑只需 13329ms 即通过。这就是 #10853 的机制及其修复,端到端复现完毕。base 在同一个测试上带的是 }, 30000);(base 第 2076 行),所以非池分支的 30s 预算在本 PR 前后逐字节相同:这次超时不是 diff 引入的回归。

另外两个非池失败属于环境问题,且都在池上那次运行中消失:

  • install-script.test.js > does not package audio-capture test artifactsENOENT: stat packages/audio-capture/dist。该目录在本 checkout 中原本不存在;执行 npm run build 之后就存在了,测试随后以 1027ms 通过。这是缺失的构建产物,不是超时。
  • verify-capture.test.js > renders 256-colour and truecolor via the default-grey fallbackexpected false to be true,所在文件本 PR 从未触碰,且它在第二次运行中通过(2983ms)—— 与发现 4 的"未复现"一致。

那次运行自身的形态指向机器池,而不是代码。 web-shell E2E Smoke 在 20.9 分钟被取消,而它的 timeout-minutes: 20。main 在同一天早上还重构了这些 job —— b8f35aa5d3 于 03:53 把 lint 与静态检查从 Test job 中拆出,035418958d 于 03:55 加入了争用重试 —— 所以 07:08 跑的那个 Test job 是 main 的新形态,而不是本分支上次见到全绿时的那个形态。Post Coverage Comment 在启动 13 秒后失败,与一个从未跑到覆盖率上传步骤的 Test job 相符。

结论。 没有证据把 Test 失败与这个 diff 关联起来,而这个 diff 也不可能调低任何超时,因此其中没有可修的东西。我并不声称该检查已被修好 —— 工作流的独立 CI 重跑才是裁定它的门禁。如果它再次变红,上表指出了首先该看哪里。

改动

一个提交,b097f06f05,一个文件,+9/−6:

  • scripts/tests/unit-vitest-configs.test.ts —— 把防遮蔽遍历放宽到套件的 include+setup 面(R3-1)。

PR 的改动范围仍是 scripts/tests/ 下的四个文件。没有触碰 PR 自身范围之外的任何区域,也没有修改任何 CI 或验证机制。本轮净 diff 增长为 +3 行测试代码,预算是 400 行。

冲突说明

--conflict false,因此没有执行 origin/main 的合并。有一点需要提请维护者注意:origin/main 已经远远越过本分支的合并基点(055e831556a8248eaed3,14 个提交,其中三个重构了 ci.yml)。由于本 PR 不触碰任何工作流文件,因此不存在冲突,但本分支尚未在最新 main 上跑绿过 CI。

验证

  • npm run build —— 通过(exit 0)
  • npm run typecheck —— 通过(exit 0)
  • npm run lint —— 通过(exit 0)
  • npx eslint scripts/tests/unit-vitest-configs.test.ts —— 通过,无发现
  • npx prettier --check scripts/tests/unit-vitest-configs.test.ts —— 通过,"All matched files use Prettier code style!"
  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/unit-vitest-configs.test.ts —— 28 通过(28),1 个文件通过;每次探针还原后重跑均为绿
  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js -t 'behaviorally replays the stale-duplicate revalidation' —— 1 通过,耗时 13329ms(单独运行的基线)
  • 完整 scripts 套件,非池分支(RUNNER_NAME 未设置)—— 73/76 文件;2094 通过 / 3 失败 / 16 跳过;三个失败均已在上方追溯
  • 完整 scripts 套件,池上分支(RUNNER_NAME=ecs-qwen-probe),在 npm run build 之后 —— 76/76 文件;2097 通过 / 16 跳过 / 0 失败;exit 0
  • 对改动文件运行针对性的 npx tsc --noEmit --strict —— 22 个错误,全部为既有 TS2322;对该文件的 base 版本运行完全相同的命令得到同样的 22 个,所以本次改动没有新增
  • 未运行集成测试,也未运行 npm run bundle:改动仅限于 scripts/tests 下一个静态分析 pin,没有任何打包 CLI 或集成路径会执行它
  • 未运行 npm run generate:settings-schema:没有改动任何 settings 源

变异探针 —— 每一个都经过植入、运行、然后还原,并在之后确认 git status --short 干净:

探针 植入内容 修复前逻辑 修复后守卫
A scripts/tests/test-setup.ts 末尾追加 vi.setConfig({ testTimeout: 1 }) 从不扫描 —— 该文件名实测过滤结果为 false expected [ 'test-setup.ts' ] to deeply equal [];还原后重新变绿
B scripts/tests/zz-probe-multikey.test.js 中写入 vi.setConfig({ hookTimeout: 15_000, testTimeout: 30_000 }) 绿 —— 这就是漏洞:标出 [] :标出该文件
C scripts/tests/zz-probe-nested/zz-probe-nested.test.js 中写入 vi.setConfig({ testTimeout: 30_000 }) 绿 —— 这就是漏洞:非递归遍历根本没看见它 :标出 zz-probe-nested/zz-probe-nested.test.js

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

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

Re-review when you have a moment. After round 10 this bot stops and leaves the PR for a human. · 有空请复审;第 10 轮后本 bot 停止并将 PR 交给人工。


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

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants