fix(ci): give the scripts test suite the shared-ECS timeout ceiling (#10853) - #10858
fix(ci): give the scripts test suite the shared-ECS timeout ceiling (#10853)#10858qwen-code-dev-bot wants to merge 6 commits into
Conversation
…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.
Autofix report — issue #10853 "Release Failed for v0.23.0 on 2026-09-02"Branch What the release run actually failedThe issue body names only
Job logs are not readable from this runner This round fixes that named failure. The workspace shards are not fixed here — Root cause of the script-lane failureThe test is healthy, not hung. It replays the real Every other suite in this repository already expects the shared self-hosted pool
The script suite is the one leg left unclamped, and it is the most Seven tests in Reproduction and mechanism evidence
The change
What this does not fix
VerificationCommands actually run in this round, in order:
Base-probe output for the unrelated 中文说明Autofix 报告 —— issue #10853「Release Failed for v0.23.0 on 2026-09-02」分支 这次发布实际失败的是什么issue 正文只写了
这台 runner 读不到 job 日志 本轮修复的就是这个具名失败。workspace 分片不在本次修复范围内 —— 见「本次没有 脚本通道失败的根因这个测试是健康的,并没有卡死。它用记录型 仓库里其他所有测试套件都已经认定共享自托管机器池会把这类健康测试拉长,并在
脚本套件是唯一没有加上限的一条,而它恰恰是仓库里子进程最密集的套件。发布通道在
复现与机制证据
改动内容
本次没有修复的部分
验证本轮实际执行的命令,按顺序:
与 🧠 Handled by Qwen Code · model/模型 |
|
|
|
Thanks for the PR! Template looks good ✓ Problem: observed, with solid evidence. Release run 33676423730 genuinely failed its Direction: aligned. 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 Risk: no elevated risk signals (no high-risk paths matched). Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:已观测到,证据充分。发布运行 33676423730 确实在 方向:对齐。 规模:不适用——未触及核心路径;共 53 行,全部为测试基础设施(配置 + 两个测试文件),零生产代码。 方案:范围合理。三处改动各自必要:上限钳制本身、移除会遮蔽新上限的七个 风险:无升级风险信号(未命中高风险路径)。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewThe diff does exactly three things, and each is load-bearing:
No correctness, security, or convention issues found. One non-blocking note: the three redundant 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 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
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 中文说明代码审查diff 恰好做了三件事,且每件都必要:
未发现正确性、安全性或约定问题。一条非阻塞提示: 测试证据——被审提交自身的 CI(本审查不执行 PR 代码)唯一的红色检查与本 PR 无关:Dependency CVE audit 因 抓取时主套件仍在运行;按工作流规则此处不轮询——finalize 任务会在 CI 落定后就地改写上表。 关于行为性主张:机制是被固定的,不只是被声称——新的奇偶测试在 CI 中运行,钳制的任一分支漂移都会使其失败,这是 PR CI 对超时上限类改动所能给出的最强信号。没有任何沙箱通道能复现机器池争用本身,因此余量是否真的让发布通道通过只能在下次发布运行中观察;这是此类修复的固有属性,不是 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — clean, minimal, well-evidenced fix; the only nit is the deliberately deferred sibling ceilings in 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 ( 中文说明置信度:4/5 —— 干净、最小化、证据充分的修复;唯一的非阻塞点是刻意延后处理的 这是一次高质量的自动修复。问题是观测到且被具名的——真实的发布运行在特定测试的特定上限处失败——而修复方式是补全既有模式,而非发明新机制:与之前五个套件相同的钳制、相同的 60 秒值、相同的奇偶套件固定约定。我独立构想的修复方案与 diff 收敛于同样的三处改动,且每处改动都可证明是必要的——被移除的七个上限否则会遮蔽钳制,作者显然理解这一点。范围把握同样诚实:同一次发布运行中的 workspace 分片明确留给 #10820,并刻意省略关闭关键字以保持 #10853 开放。六个月后回看,这就是一次恰如其分的例行维护。 准备批准——但审查时 PR 自身的 CI( — Qwen Code · qwen3.8-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not explored to full depth (tool budget reached): "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)
|
🔀 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
left a comment
There was a problem hiding this comment.
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)
Deep verification (local maintainer round, 2026-09-03) — verdict: merge-ready94/94 scripted assertions passed, 0 unexpected failures, across six harnesses ( 中文摘要结论:
Central claim: the clamp fires on the pool, and only thereProbe = a scratch test in
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. The reported failure needs BOTH halvesThe test the release lane named is itself one of the seven (base lines 13536–14756 close with
The new parity pin is not vacuousEach 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.
The 60s ceiling against the suite's own documented RPC deadline
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). Production witness and the lane-scale gateFrom the failing job of release run 33676423730 (readable with maintainer access): Whole-suite A/B, both cells with Margin off production data (526 test-level rows of the real run): the lane's slowest test was Corrections to the description (not to the code)
Findings (none blocking)
Not coveredNon-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: Methodology, harnesses ( |
…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.
|
🤖 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 Findings and dispositionsR1-1 —
|
| 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-progressis true for every ref exceptmainand
release/*, so each push to this branch cancels the run in flight. This branch
carries threeMerge branch 'main'commits, i.e. three superseding pushes.post_coverage_commenthasneeds: [classify_pr, test], is
continue-on-error: trueby design, and its first substantive step downloads the
coverage-reports-22.x-ubuntu-latestartifact that only the cancelledtestjob
uploads — so a cancelledtestmakes it fail with nothing to consume.- That job reads only
cli/coverageandcore/coveragesummaries. This PR touches
zero files underpackages/cliorpackages/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-workflowreproduces 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
#d4d4d4pixel in
text that sharp/librsvg rasterises from an SVG requestingDejaVu Sans Mono. This
sandbox has no fontconfig (fc-listunavailable, 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 editnpm run typecheck— passed (exit 0); run twicenpm run lint— passed (exit 0); run twicenpx prettier --checkon the four touched files — passed (exit 0); run twicenpx vitest run --config ./scripts/tests/vitest.config.ts unit-vitest-configs— 28 passed; also green withRUNNER_NAMEset and unsetnpx vitest run --config ./scripts/tests/vitest.config.ts verify-capture— 23 passed in isolationnpm run test:scripts(full scripts lane, 76 files) — run 1: 75 files passed | 1 failed (theverify-captureflake), 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-configs28/28,install-script126 tests (16 skipped),qwen-autofix-workflow229/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):
- 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. - Unset-
RUNNER_NAMEarm. Deleted?.fromscripts/tests/vitest.config.ts→
pin GREEN 28/28 withRUNNER_NAME=fv-az123-ubuntu(hole confirmed) and
failed to load config ... TypeError: Cannot read properties of undefined (reading 'startsWith')withRUNNER_NAMEunset. Added the unset arm → same
mutation now RED under the CI condition. Restored?.→ GREEN under both
conditions, andgit diff scripts/tests/vitest.config.tsis empty, so no mutation
leaked into the commit. - Pre-fix reproduction of R1-1. Two scratch files under
scripts/tests/, config
ceiling 60_000 viaRUNNER_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 at2004ms. Both scratch files deleted
afterwards;git statusshows 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 上的失败检查
Test 与 web-shell E2E Smoke 为 CANCELLED,Post Coverage Comment 为 FAILURE。依据 .github/workflows/ci.yml(只读——.github/ 属于禁止修改范围)诊断为取消级联,而非代码缺陷:
concurrency.cancel-in-progress对除main与release/*之外的所有 ref 都为 true,因此向本分支的每次推送都会取消正在进行的那次运行。本分支带有三个Merge branch 'main'提交,即三次相互取代的推送。post_coverage_comment的needs是[classify_pr, test],设计上带continue-on-error: true,而其第一个实质步骤要下载只有被取消的test作业才会上传的coverage-reports-22.x-ubuntu-latest产物——因此test被取消会让它在无产物可用的情况下失败。- 该作业只读取
cli/coverage与core/coverage摘要。本 PR 在packages/cli或packages/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-captureflake),exit 1。第 2 次,代码树相同:76 文件通过,2097 通过 | 16 跳过,exit 0。第 3 次,最终代码树:75 通过 | 1 失败(同一 flake),exit 1。三次运行中本轮触碰的每个文件都通过:unit-vitest-configs28/28、install-script126 个测试(16 跳过)、qwen-autofix-workflow229/229。- 配对运行
verify-capture qwen-autofix-workflow— 1 失败(同一 flake)| 251 通过,exit 1;用于隔离出负载机制
变异探针(本轮新增的每个守卫都有自己的见证):
- 遮蔽覆盖断言。 向
install-script.test.js重新加回vi.setConfig({ testTimeout: 30_000 });→ 断言红:expected [ 'install-script.test.js' ] to deeply equal []。再次移除 → 绿 28/28。这次绿色运行同时证明该断言不会匹配到自己的正则源码。 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为空,说明没有变异泄漏进提交。- 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
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
中文说明
已审查。 建议见行内评论。
— qwen3.8-max via Qwen Code /review (v0.22.3)
…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.
|
🤖 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: Feedback dispositions[rc:3922637899] R3-1 (Suggestion) — IMPLEMENTED, resolved in codeThe 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 (
Fix, in
False-positive check: [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 changeThis 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 Finding 1 — Finding 2 — 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 Finding 5 — Failed checks
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 Reproducing the #10853 failure on this host. Full scripts suite, same tree, same host, two arms:
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 The other two off-pool failures are environmental, and both cleared on the on-pool run:
The run's own shape points at the pool, not the code. 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. ChangesOne commit,
The PR footprint stays at four files under Conflict notes
Verification
Mutation probes — each planted, run, then reverted, with
中文说明第 4 轮 —— PR #10858(issue #10853)本轮一个提交: 反馈处置[rc:3922637899] R3-1(Suggestion)—— 已实现,代码中已解决该发现指出:上一轮新增的防遮蔽 pin,其防守面比它要抓的遮蔽面更窄。三条主张都在动手之前对照被审查的那个提交(
修复仅涉及
误报核查: [rv:5099647405] review 正文 —— 无需处理"Reviewed. Suggestions are inline."(已审查,建议见行内评论。)它本身不承载任何主张,只是上面那条行内发现的封面。 [ic:5521567151] @wenshao 深度验证(结论:merge-ready)—— 已确认,不改代码这是一份验证报告而非修改请求,其中没有任何内容要求产生 diff。对其内容的处置如下: 对描述的更正(worker 数是 8 不是 4;三个 workspace 分片的失败已被指名且无一是 发现 1 —— 发现 2 —— 发现 3 —— 新的 60s 上限恰好等于套件自己记载的 60s worker→main RPC 期限:已确认,不做处理。 同一份报告的 S1/S2 探针表明,更可怕的那种"全绿但 exit 1"模式在 65s 并不成立(取而代之的是一次干净、具名的 60000ms 超时)。用已证实的 #10853 失败去换一点纸面余量,是划不来的交换。 发现 4 —— 描述预测的 发现 5 —— 失败的检查
我无法读取 job 日志 —— 本轮不持有任何 GitHub 凭据 —— 所以改为本地复现,并追溯那次运行实际执行了什么。 这个 diff 能碰到什么、碰不到什么。 PR 的全部改动范围是 在本机复现 #10853 的失败。 完整 scripts 套件,同一棵树、同一台主机、两个分支:
该测试在整套并发争用下确实需要 33.3 秒 —— 超过旧的固定 30s,落在新的 60s 之内 —— 而单独跑只需 13329ms 即通过。这就是 #10853 的机制及其修复,端到端复现完毕。base 在同一个测试上带的是 另外两个非池失败属于环境问题,且都在池上那次运行中消失:
那次运行自身的形态指向机器池,而不是代码。 结论。 没有证据把 Test 失败与这个 diff 关联起来,而这个 diff 也不可能调低任何超时,因此其中没有可修的东西。我并不声称该检查已被修好 —— 工作流的独立 CI 重跑才是裁定它的门禁。如果它再次变红,上表指出了首先该看哪里。 改动一个提交,
PR 的改动范围仍是 冲突说明
验证
变异探针 —— 每一个都经过植入、运行、然后还原,并在之后确认
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/模型 |






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
qualitygate and the release was not published.qualityis 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 merge—Error: Test timed out in 30000ms.That test is not hung. It replays the real deferred-findings upsert script against a recording
ghstub 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_NAMEstarts withecs-qwen-:packages/core,packages/cliandpackages/acp-bridge(#8982, #10552),packages/vscode-ide-companion(#10672), andpackages/web-shell(#10765, merged about fifteen hours before this release run).scripts/tests/vitest.config.tswas the one left with a flat 30s — and it is the most subprocess-heavy suite in the repo, run by both the releasequality_scriptslane and main CI'snpm 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
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.git difftouches three files underscripts/tests/and no assertion.npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/unit-vitest-configs.test.ts→ 27 passed. Then flatten60_000to30_000inscripts/tests/vitest.config.tsand re-run → the new pin fails withRUNNER_NAME=ecs-qwen-parity: expected 30000 to be 60000; restore it and the suite is green again.scripts/tests/, write one test that sleeps 35s with no explicit ceiling and one that sleeps 2s with, 1000). WithRUNNER_NAMEunset the 35s test fails atTest timed out in 30000ms; withRUNNER_NAME=ecs-qwen-probeit passes at ~35s, while the, 1000)test fails at 1000ms in both arms.npm run test:scripts. Expect the pre-existing, environment-dependentverify-capturepixel 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):
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.ecs-qwen-*Tested on
ecs-qwen-*runners and the non-ECS value is unchangednpm run build,npm run typecheck,npm run lint,npm run test:scripts, plus the reproduction and mutation probesEnvironment (optional)
Unit tests only, on a self-hosted Linux runner with
RUNNER_NAMEunset (so the non-ECS branch is what the local runs exercised) plus explicitRUNNER_NAME=ecs-qwen-*stubs for the ECS branch. No CLI, sandbox, or network-dependent flow was run.Risk & Scope
spawnSyncin 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.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 noVITEST_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 inpackages/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 inscripts/tests/upload-aliyun-oss-assets.test.jsof the same class as the seven removed here.npm run test:scriptson 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_scriptslane 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_NAME以ecs-qwen-开头时抬高上限:packages/core、packages/cli与packages/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)上限不动。审阅测试计划
如何验证
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 秒通过,也就是说这一直是上限问题,不是卡死。git diff只涉及scripts/tests/下的三个文件,且没有任何断言变化。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失败;恢复后套件重新变绿。scripts/tests/下写一个临时测试文件,其中一个测试 sleep 35 秒且不带显式上限,另一个 sleep 2 秒并带, 1000)。RUNNER_NAME未设置时,35 秒的测试以Test timed out in 30000ms失败;RUNNER_NAME=ecs-qwen-probe时它在约 35 秒通过,而带, 1000)的测试在两种环境下都在 1000ms 失败。npm run test:scripts。如果你的沙箱字体渲染不同,预期会出现 Risk & Scope 中描述的、既有的且依赖环境的verify-capture像素失败;其他测试全部通过。证据(改动前后)
改动前 —— 发布通道当时所处的上限,在一台有争用的机器上复现(并行运行完整 workspace 套件,64 核上 load ~150):
改动后 —— 同一台机器、同一个测试、
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-*上的上限已测试平台
ecs-qwen-*runner 上触发,非 ECS 数值未变npm run build、npm run typecheck、npm run lint、npm run test:scripts,外加复现与变异探针环境(可选)
仅单元测试,在一台自托管 Linux runner 上进行,
RUNNER_NAME未设置(因此本地运行实际走的是非 ECS 分支),另外用显式的RUNNER_NAME=ecs-qwen-*桩验证 ECS 分支。没有运行 CLI、沙箱或依赖网络的流程。风险与范围
spawnSync仍保留自己的 30 秒限制,因此卡死的子进程仍会在 30 秒被切断。非 ECS 数值与原来逐字节相同,所以本地运行和 macOS/Windows 通道不受影响。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-capture、chrome-extension、webui)仍以 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 保持开放。