fix(release): cap Vitest workers in the quality_scripts lane (#10755) - #10760
Conversation
The 2026-09-02 nightly failed on the quality aggregate because the quality_scripts lane (added by the release-sharding split, #10619) ran the scripts suite with vitest's default worker count — one worker per host core — on the shared ECS pool. On a 64-core runner executing several pool jobs at once, the suite's ESLint instances and bash replays spawned dozens of heavy subprocesses in parallel and 5-8 tests per run blew through the 30s test timeout non-deterministically (reproduced twice on the failing SHA: 30s timeouts across the ESLint boundary guards and workflow replays, plus a spawn-latency race in the upload-stall test). The sibling workspace_tests lane and the main CI gate already bound their vitest workers on ECS (#10667); the new lane never inherited the bound. Apply the same tunable per-process bound to the lane and pin it in the release contract test next to the workspace lane's copy. Also deflake the upload-stall test: its 400ms kill bound raced child-spawn latency under pool load, losing attempt log lines and reading 1 or 2 attempts instead of 3; the bound is now 2s — still far under the shim's 60s hang, so the kill semantics are unchanged, and the worst-case run stays inside the test's own 30s budget.
Autofix report for #10755 — Release Failed for v0.22.3-nightly.20260902.77d41f48d3Root causeThe nightly release failed on the Vitest's default worker count equals the host core count. On the shared pool machines (64 cores here, load average ~120 from concurrent jobs), the scripts suite — whose tests instantiate repo-wide ESLint engines and replay extracted workflow bash via spawned subprocesses — launched ~64 parallel workers, each spawning heavy children. Under that contention, 5–8 tests per run blow through the suite's 30s test timeout, and the victim set changes every run. Reproduction (on the exact failing SHA 77d41f4)
Fix
The release.yml growth is 513 bytes, inside the size-ratchet allowance, so Verification
中文说明#10755 自动修复报告 — v0.22.3-nightly.20260902.77d41f48d3 发布失败根因夜间发布在 Vitest 的默认 worker 数等于主机核心数。在共享资源池机器上(本机 64 核,因并发任务负载均值约 120),脚本测试套件中的测试会实例化全仓库 ESLint 引擎、并通过派生子进程回放从工作流中逐字提取的 bash 片段——套件以约 64 个并行 worker 运行,每个 worker 又派生重量级子进程。在这种资源争用下,每次运行有 5–8 个测试突破套件的 30 秒测试超时,且每次运行中招的测试集合都不同。 复现(在完全相同的失败 SHA 77d41f4 上)
修复
release.yml 增长 513 字节,在 size-ratchet 允许范围内,因此无需更新 验证
🧠 Handled by Qwen Code · model/模型 |
|
Re-run after the branch merged main (head moved from Template looks good ✓ Problem: observed, not theoretical. The 2026-09-02 nightly release failed on the Context change since the first pass: the headline fix — capping Vitest workers on the Direction: aligned — release-lane reliability. Test-only change, zero production impact. Size: not applicable (no core paths; one test file). Approach: minimal, and the right minimal. The kill bound runs from spawn ( Risk: no elevated risk signals (no high-risk paths touched). Moving on to code review. 🔍 中文说明分支合并 main 后的重新运行(head 从 模板完整 ✓ 问题:已观测到,非理论性问题。2026-09-02 的夜间发布在 与首轮评审相比的关键变化: 重头修复——给 方向:对齐——发布分支可靠性。纯测试改动,对生产行为零影响。 规模:不适用(未触及核心路径;单个测试文件)。 方案:最小化,且是正确的最小化。kill 计时从 spawn 开始( 风险:无升级风险信号(未触及高风险路径)。 进入代码审查 🔍 — 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 — no check was cut short..
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
已审查。 建议见行内评论。
未探索到全部深度(达到工具调用预算):"agent 1d":none — no check was cut short.。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max via Qwen Code /review (v0.22.3)
|
E2E review report (tmux-based, head bc66afd) — no Critical found. Static verification
Live run (scratch tree of the PR head + symlinked node_modules, vitest in tmux)
Caveat: the release workflow runs on tag/schedule events, not on this PR, so the affected lane cannot be CI-exercised here — the local suite + mutation evidence above is what covers the change. PR CI at head: all completed lanes green; Not approving: zero gate approvals at head (only ci-bot COMMENTED 03:23Z). |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
|
🐑 Merge conflict with main detected — dispatched the autofix loop to resolve it. / 检测到与 main 的合并冲突,已触发 autofix 处理。 |
…lanes (#10755) Review of the worker-cap fix noted that the Run Script Tests step carried a byte-identical copy of the Run Workspace Tests env block, so any future tuning (bumping the '4' default, adding another pool/thread variable) would need two synchronized edits, and editing one step while missing the other would silently restore the unbounded-worker timeout on the untouched lane — caught only reactively by a red contract test. Anchor the block at its first occurrence (&vitest_worker_bound) and alias it into the scripts lane, the same mechanism this file already uses for &release_test_env across six jobs. Reshape the contract assertions to pin the structure: the bound's four variables at the anchor definition and the bare alias at the consumer, so dropping either side turns the test red. Net five lines removed.
|
🤖 Addressed the latest review feedback (round 1/10). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/10 轮)。改动内容与我反驳保留之处如下: Autofix review round — PR #10760 (issue #10755)Commit: Feedback dispositions1. The step-level
Mutation witness (as the finding requested):
2. 3. 4. Failed check Run 33583720384 started 02:35:37Z and failed 03:47:16Z (~72 min; a FAILURE conclusion, not a timeout cancellation — the ECS ceiling is 120 min). This runner holds no GitHub credentials, so the lane logs are not fetchable; the diagnosis below is from the available evidence:
Conclusion: a pool-saturation casualty in the lane's core-suite segment, unrelated to this diff. The new push re-runs the lane, and the workflow's independent CI remains the final gate. Deferred finding (outside this PR's footprint)Recorded in VerificationCommands actually run this round (on the committed tree
中文说明Autofix 审查轮次 — PR #10760(issue #10755)提交: 反馈处置1.
变异见证(按该建议的要求):
2. 3. 4. 失败检查 运行 33583720384 于 02:35:37Z 开始,03:47:16Z 失败(约 72 分钟;结论是 FAILURE 而非超时取消 — ECS 上限为 120 分钟)。本运行器没有 GitHub 凭据,无法拉取该分支日志;以下诊断基于现有证据:
结论:该分支 core 套件段在资源池饱和下的一次偶发伤亡,与本 PR 改动无关。新提交会重跑该分支,工作流的独立 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/模型 |
|
Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with |
|
🐑 Merge conflict with main detected — dispatched the autofix loop to resolve it. / 检测到与 main 的合并冲突,已触发 autofix 处理。 |
main's 7df5ac6 (#10765) landed the same Vitest worker bound this branch added to the release scripts lane, and pinned it with its own contract test asserting the four VITEST_* variables literally in both lanes. Resolve both conflicts to main's shape: its fix is the superset (dedicated hk4 host, serialized concurrency, rescheduled crons, nightly/preview retry), and its workspace lane now carries a lane-only VITEST_RETRY that an env-block alias would leak into the scripts lane. That drops this branch's YAML anchor dedup of the two env blocks, so the reviewer Suggestion it answered stays open for a maintainer to call: re-applying it now means rewriting the contract test #10765 just merged. What survives is the orthogonal deflake in the upload-stall test. Its 400ms attempt bound raced node's cold-start latency, so the SIGKILL could land before the shim's first appendFileSync and lose that attempt's log line. Measured under 20-way single-CPU contention, 400ms loses the log entirely (ENOENT reading the attempt count) while 2000ms passes, worst case 12.2s inside the test's 30s budget.
|
🤖 Addressed the latest review feedback (round 2/10). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/10 轮)。改动内容与我反驳保留之处如下: Round summary — base-conflict resolution; main superseded this PR's headline fix
There were no new review bodies and no new inline comments this round. The only new feedback was the review-pipeline fallback notice, plus the base conflict. The conflict: main landed the same fix
#10765 is the strict superset of this PR's CI change: it additionally pins every release validation job to a dedicated Both conflicts were resolved to main's shape, per file:
Net result: the PR now changes one file, and no longer touches [ic:5505682166]
|
| Probe | Result |
|---|---|
Mechanism, timeout: 1ms |
ETIMEDOUT, 0 log lines written — kill before appendFileSync loses the line |
| Unloaded node cold start (n=12) | min 18ms / p50 20ms / max 24ms |
| 128 busy-loop burners (n=12) | min 34ms / p50 61ms / max 238ms — 400ms is only ~1.7× the worst start |
| 24-way concurrency pinned to 1 CPU, bound 400ms | 58 of 96 attempts lost their log line |
| Same pressure, bound 2000ms | 0 of 48 lost |
Mutation probe on the real test (guard restored afterwards, re-run to green):
- Reverted
attemptTimeoutMsto400and ran the single test with vitest and 20 burners all pinned to CPU 0 → FAILED:ENOENT: no such file or directory, open '/tmp/qwen-upload-hang-cpNvSy/ossutil.log'at line 215. All three attempts were killed before their first write, so the log never existed — a harsher symptom than the short count the round-1 commit described, and the reason the comment now names both. - Restored
2000under identical contention → PASSED in 12166ms, inside the test's own 30s budget (worst case 3 × 2s kills + 2s + 4s backoff = 12s).
The comment in that test was sharpened to state the observed symptom and the arithmetic rather than the vaguer "well above worst-case latency on the shared runners".
Open question for a maintainer — PR scope (not blocking)
With #10765 merged, this PR no longer changes any CI configuration; its whole remaining content is the upload-stall deflake, while its title and body still describe capping Vitest workers in the release scripts lane. Retitle and narrow the PR to the deflake, or close it and carry the deflake in a follow-up?
Recommendation: keep it open and retitle to something like test(scripts): keep the upload-stall attempt bound above child-spawn latency (#10755). The deflake is verified, orthogonal to #10765, and one file / +7 −1.
Open question for a maintainer — [rc:3910422172] dedup Suggestion
Left unresolved on its thread (reply posted there). Main's #10765 keeps the four variables duplicated byte-identically across both lanes and pins both copies with a contract test asserting the literal strings, so the reviewer's drift concern still describes main's code — but acting on it now means restructuring freshly-merged CI YAML and rewriting that test. Two defensible directions, and the call is not mine: leave main's explicit duplication (drift is caught reactively by the contract test), or dedup in a small follow-up against main. Recommendation: the latter if wanted at all — not inside this PR.
Boundaries respected
No .github/ content is modified by this PR any more; the net diff is one file under scripts/tests/, which is ordinary test code. No test was deleted or weakened — the surviving change raises a timeout bound and its assertion (toHaveLength(3), toThrow(/ossutil failed after 3 attempts/)) is unchanged. Commits are additive; no history rewrite. A local git identity had to be configured in this checkout (none was set, so git merge could not create a commit); it matches the identity of the branch's existing commits.
Verification
Commands actually run, in order, on the merged tree:
npm run build— passed (exit 0)npm run typecheck— passed (exit 0)npm run lint— passed (exit 0); run twice, once after conflict resolution and once after the comment editnpx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/upload-aliyun-oss-assets.test.js scripts/tests/package-scripts.test.js scripts/tests/release-workflow.test.js— 3 files / 81 tests passed (exit 0); run twice, once after conflict resolution and once after the comment edit. The two release contract suites are included because they pin the workflow file whose conflict was resolved.npx prettier --check scripts/tests/upload-aliyun-oss-assets.test.js— passed (exit 0)npm run generate:settings-schema— ran clean (exit 0) and produced output byte-identical to the merged artifact (git diffempty forpackages/vscode-ide-companion/schemas/settings.schema.json), so the schema main brought in is fresh and nothing extra needed committing. Checked because main's 26 commits touchedpackages/cli/src/config/settingsSchema.tsand CI has a schema-freshness step that build/typecheck/lint/vitest cannot see.- Mutation probe,
attemptTimeoutMs2000 → 400, single test under 20-way single-CPU contention — FAILED as intended (ENOENTon the attempt log, line 215); restored to 2000 under identical contention — PASSED (12166ms). git status --short— clean, no untracked files, no unmerged paths.
Not run: npm run bundle and the integration suites. The touched behavior is a unit-level test under scripts/tests/ executed by npm run test:scripts; it is not reachable only through the bundled CLI or the integration harness, so bundling would not exercise it.
中文说明
本轮小结 —— 解决基线冲突;main 已合入本 PR 的核心修复
本轮收到 --conflict true,因此把 origin/main(26 个提交,77d41f48d3..f4937c1854)合并进 autofix/issue-10755,并解决了两处内容冲突。合并提交为 5b10cc2c3b(父提交 09c6e81ca6、f4937c1854);当前分支落后 main 0 个提交。
本轮没有新的 review 正文,也没有新的行内评论。唯一的新反馈是审查流水线的兜底通知,加上这个基线冲突。
冲突根源:main 合入了同一个修复
7df5ac6898 —— fix(ci): isolate and bound release validation workloads (#10765) —— 在本分支切出之后合入 main,独立地给 quality_scripts 的 Run Script Tests 步骤加上了完全相同的四个 Vitest worker 上限变量(VITEST_MAX_THREADS / MIN_THREADS / MAX_FORKS / MIN_FORKS,相同的 ecs-qwen- 条件、相同的 '4' 默认值),而这正是本 PR 第一个提交所做的事情。它还用自己的契约测试在 scripts/tests/package-scripts.test.js 中固定了这个上限:遍历两条 lane,逐一断言变量字面量。
就 CI 改动而言,#10765 是本 PR 的严格超集:它额外把所有 release 校验作业钉到专用的 ecs-qwen-hk4-host 标签、用 concurrency 组串行化定时校验、把两个 cron 移出资源池高峰时段、增加了仅用于 nightly/preview 的 VITEST_RETRY,并给 packages/web-shell 配上了共享 ECS 的超时上限。
两处冲突都按文件解决为 main 的形态:
.github/workflows/release.yml→ 采用 main 版本,逐字节相同。本分支在该文件上剩下的改动只有第二轮的 YAML 锚点/别名(&vitest_worker_bound/*vitest_worker_bound)。在 fix(ci): isolate and bound release validation workloads #10765 之上重新套用它会把仅属于 workspace lane 的VITEST_RETRY泄漏进 scripts lane,因为锚点挂在被 fix(ci): isolate and bound release validation workloads #10765 扩展过的Run Workspace Testsenv 块上 —— 想避免泄漏就得再加一个独立的锚点承载节点并用<<:合并,也就是引入更多 CI 机制,而不是更少。scripts/tests/package-scripts.test.js→ 采用 main 版本,逐字节相同。main 的循环已对两条 lane 都断言了该上限,所以放弃本分支的改写并不会削弱对 worker 上限的覆盖;而保留改写就意味着去重写一个四小时前才合入的契约测试。scripts/tests/upload-aliyun-oss-assets.test.js→ 无冲突,保留本分支的改动(见下文)。
最终结果:本 PR 现在只改动一个文件,并且完全不再触碰 .github/。
[ic:5505682166] review-pr: FAILURE —— 无法在代码层面处理
这是自动审查流水线自身失败,不是针对本 PR 代码的门禁。来自 checks.json 的证据:review-pr 于 07:01:09Z 开始、08:32:09Z 结束,结论为 FAILURE;随后工作流自己的 fallback-comment 作业于 08:32:12Z 开始、08:32:15Z 结束并成功,发出了「审查未能成功完成」的通知。列表中其它所有检查均为 SUCCESS 或 SKIPPED,没有任何 build、lint、typecheck 或 test 检查失败。
该作业的超时上限是 vars.QWEN_REVIEW_JOB_TIMEOUT_MINUTES(文档默认 360),所以 91 分钟并不是作业级超时 —— 流水线是在发布结果之前内部失败的。我没有作业日志,因此不断言具体的内部原因。可以确定的是:工作流把它归类为瞬时故障,其设计好的重试方式需要人工触发(@qwen-code /review),而修复位置会在 .github/workflows/qwen-code-pr-review.yml —— 那是审查机制本身,对本 PR 属于禁区,也不在其 footprint 之内。未做代码改动;需要机器人/维护者重试。 单独来看,重跑审查本身也有价值,因为它现在要审查的 diff 只有一个测试文件。
保留:上传卡死测试的去抖动修复,现已通过探针验证
scripts/tests/upload-aliyun-oss-assets.test.js 也会在常规 PR 门禁中运行(test:ci → test:scripts),并不只在 release lane 里跑,所以 #10765 的主机钉定并不能让它变得多余。它 400ms 的尝试超时与 node 的冷启动延迟存在竞争:spawnSync(..., { timeout, killSignal: 'SIGKILL' }) 从 spawn 那一刻开始计时,而 shim 的第一条语句正是测试要统计的 appendFileSync —— 因此如果 kill 在 node 完成启动之前落下,该次尝试的日志行就丢了。
在这台 64 核 runner 上的实测:
| 探针 | 结果 |
|---|---|
机制验证,timeout: 1ms |
ETIMEDOUT,写入日志行 0 条 —— kill 早于 appendFileSync,日志行丢失 |
| 无负载 node 冷启动(n=12) | min 18ms / p50 20ms / max 24ms |
| 128 个忙循环加压(n=12) | min 34ms / p50 61ms / max 238ms —— 400ms 仅为最坏启动时间的约 1.7 倍 |
| 24 路并发钉在单 CPU,超时 400ms | 96 次尝试中 58 次丢失日志行 |
| 同等压力下,超时 2000ms | 48 次中 0 次丢失 |
对真实测试做的变异探针(守卫随后已恢复,并重跑至全绿):
- 把
attemptTimeoutMs改回400,在 vitest 与 20 个加压进程全部钉在 CPU 0 的条件下运行该单个测试 → 失败:ENOENT: no such file or directory, open '/tmp/qwen-upload-hang-cpNvSy/ossutil.log',位于第 215 行。三次尝试都在首次写入之前被杀掉,日志文件根本不存在 —— 这比第一轮提交所描述的「计数偏少」更严重,也正是注释现在把两种症状都写出来的原因。 - 在完全相同的压力下恢复为
2000→ 通过,耗时 12166ms,落在该测试自身的 30s 预算之内(最坏情况为 3 × 2s 的 kill 加 2s + 4s 退避 = 12s)。
该测试中的注释已改写为陈述实测症状与算术依据,而不是原先较含糊的「在共享 runner 上远高于最坏子进程启动延迟」。
待维护者决定 —— PR 范围(不阻塞本轮)
在 #10765 合入之后,本 PR 已不再改动任何 CI 配置;它剩下的全部内容就是上传卡死测试的去抖动修复,而它的标题与正文仍然在描述「给 release scripts lane 的 Vitest worker 设上限」。是把 PR 重命名并收窄到这个去抖动修复,还是关闭它、把该修复放到后续 PR?
建议: 保持开启并重命名,例如 test(scripts): keep the upload-stall attempt bound above child-spawn latency (#10755)。这个去抖动修复已验证,与 #10765 正交,且只有一个文件 / +7 −1。
待维护者决定 —— [rc:3910422172] 去重建议
该线程保持未解决状态(已在其中回复)。main 的 #10765 让这四个变量在两条 lane 中仍然逐字节重复,并用断言字面量的契约测试固定了两份副本,所以审查者提出的漂移隐患依然适用于 main 的代码 —— 但现在要处理它,就意味着重构刚合入的 CI YAML 并重写那个测试。两个方向都站得住脚,而这个判断不该由我来做:要么保留 main 的显式重复(漂移由契约测试被动兜住),要么在对 main 的小型后续 PR 中去重。建议:如果确实想做,走后者 —— 不要放在本 PR 里。
遵守的边界
本 PR 已不再修改任何 .github/ 内容;净 diff 只有 scripts/tests/ 下的一个文件,属于普通测试代码。没有删除或削弱任何测试 —— 保留下来的改动只是抬高了一个超时上限,其断言(toHaveLength(3)、toThrow(/ossutil failed after 3 attempts/))保持不变。提交均为追加式,未重写历史。本 checkout 中必须配置本地 git 身份(原先没有配置,git merge 无法创建提交);所用身份与该分支现有提交的身份一致。
验证(Verification)
以下是在合并后的树上真实执行过的命令,按顺序列出:
npm run build—— 通过(exit 0)npm run typecheck—— 通过(exit 0)npm run lint—— 通过(exit 0);执行两次,一次在冲突解决后,一次在注释修改后npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/upload-aliyun-oss-assets.test.js scripts/tests/package-scripts.test.js scripts/tests/release-workflow.test.js—— 3 个文件 / 81 个测试全部通过(exit 0);执行两次,一次在冲突解决后,一次在注释修改后。之所以纳入两个 release 契约测试套件,是因为它们固定的正是本轮解决冲突的那个工作流文件。npx prettier --check scripts/tests/upload-aliyun-oss-assets.test.js—— 通过(exit 0)npm run generate:settings-schema—— 正常执行(exit 0),输出与合并进来的产物逐字节相同(packages/vscode-ide-companion/schemas/settings.schema.json的git diff为空),说明 main 带来的 schema 是最新的,无需额外提交。之所以检查,是因为 main 的 26 个提交改动了packages/cli/src/config/settingsSchema.ts,而 CI 有一个 schema 新鲜度检查步骤,是 build/typecheck/lint/vitest 都看不到的。- 变异探针:
attemptTimeoutMs2000 → 400,在 20 路单 CPU 压力下运行单个测试 —— 如预期失败(第 215 行读取尝试日志时ENOENT);在同等压力下恢复为 2000 —— 通过(12166ms)。 git status --short—— 干净,无未跟踪文件,无未合并路径。
未执行:npm run bundle 与集成测试套件。被改动的行为是 scripts/tests/ 下由 npm run test:scripts 执行的单元级测试,并非只能通过打包后的 CLI 或集成测试框架才能触达,因此打包并不会额外覆盖它。
Base-conflict check · 基分支冲突检查: conflicted with main — resolved in this push. · 与 main 有冲突——已在本次推送中解决。
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.
Not explored to full depth (tool budget reached): "agent 1b": none — ~10 tool calls used of the ~32 allowance; no check was cut short..
Not reviewed: reverse audit — stopped before round 1 by the review time budget.
中文说明
未探索到全部深度(达到工具调用预算):"agent 1b":none — ~10 tool calls used of the ~32 allowance; no check was cut short.。
未审查:反向审计——评审时间预算不足,未能开始第 1 轮。
— qwen3.8-max-2026-09-02 via Qwen Code /review (v0.22.3)
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round: no action takenThis round produced no code change and no commit. Nothing in the feedback Feedback triage[rv:5088610357] — automated reviewer, The downgrade reason has since resolved on its own: the review ran against this No inline comments and no issue-level comments were present this round, so The cancelled check
Every other reported check is What this needs is a re-run of that lane, not a code change. The only lever Diff growthNet diff versus this counting window's baseline is source -6 / test -8 lines Re-verification of the PR's existing contentAlthough no feedback requested a change, the single hunk was re-checked as a
No new guard, branch, or behaviour was added this round, so no mutation probe was VerificationCommands actually run this round, and their results. No commit was made, so the
中文说明Autofix 审查轮次:未采取任何操作本轮没有产生任何代码改动,也没有提交。反馈中没有指出任何缺陷,而唯一非绿色的检查项与本 PR 无关。 反馈分类[rv:5088610357] —— 自动审查机器人, 该降级理由此后已自行消解:审查是针对这个确切的 head SHA( 本轮没有行内评论,也没有 issue 级别的评论,因此没有需要解决、拒绝、延后或上报的内容。 被取消的检查项
其余所有上报的检查项都是 这里需要的是重跑该通道,而不是改动代码。 唯一的手段是在 Diff 增长情况相对于本计数窗口的基线,净 diff 为源码 -6 行 / 测试 -8 行,而预算为 400/400,此前没有任何轮次超出预算。本窗口内 PR 是净减少的,因此无需进行合并或删减。反馈中不存在 对 PR 现有内容的重新验证虽然没有反馈要求改动,但这一处唯一的 hunk 仍以怀疑性审查者的视角重新核对过,而不是默认它正确——因为它提高了一个超时常量,并新增了一段其算术可被检验的理由说明:
本轮没有新增任何守卫、分支或行为,因此不适用变异探针(mutation probe)。 验证(Verification)本轮实际执行的命令及其结果。由于没有提交,完整的 build/typecheck/lint 门禁未在本地重跑;CI 已针对这个确切的 head SHA 执行过。
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 18 passed · 0 failed · 18 total Flakiness gate: ✅ 1 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:18 通过 · 0 失败 · 18 总计 抖动门:✅ 1 changed test file(s) x 5 identical rounds, no divergence Verification reportPR #10760 deep verification —
|
| cell (regime / bound) | attempts=3 | failure mode | max wall |
|---|---|---|---|
| natural / 400ms | 5/5 | — | 7.23s |
| natural / 2000ms | 5/5 | — | 12.04s |
| amplified / 400ms | 0/5 | ENOENT ×5 — log never created | 7.48s |
| amplified / 800ms | 1/5 | short counts ×4 (1-2 of 3) | 8.70s |
| amplified / 2000ms | 5/5 | — | 12.23s |
The race is real and deterministic at 400ms under the author's profile, 800ms still loses attempts, 2000ms is clean. Wall times match the nominal worst case 3T + 6s to within ~230ms total kill overshoot.
Vitest-level A/B — identical scenario, the actual test file, same amplified contention (logs/03-vitest-ab.log, witness evidence/02-vitest-head-green.png):
| cell | oracle | result |
|---|---|---|
| base: 400ms (single-hunk revert in scratch copy) | vitest exit / test status | 1 failed / 15 passed, exit 1 — ENOENT: no such file or directory, open '/tmp/qwen-upload-hang-oMUFoy/ossutil.log' at the test's own attempt-count read (line 217) |
| head: 2000ms (PR HEAD) | same | 16 passed, exit 0, tests 24.18s |
The base failure is the exact shape the PR describes, failing the intended assertion — not an import/fixture break.
Budget claim: 3 × 2000ms + (2s + 4s backoff) = 12.0s nominal; measured worst 12.23s under 20-way single-CPU contention; the test's own timeout is 30s (it(..., 30_000), suite testTimeout: 30_000). Margin 2.45×. The absolute ceiling for the constant while keeping the worst case ≤ 30s is ~8s (3×8+6=30), so the tuning band is (spawn-latency ceiling, ~8s) — 2s sits inside with headroom on both sides.
Premise context: the worker bound the title names (inherited from base)
Not this PR's delta, but verified since the fix story rests on it:
- Vitest 3.2.7 natively reads
VITEST_MAX_THREADS/VITEST_MIN_THREADS/VITEST_MAX_FORKS/VITEST_MIN_FORKS(node_modules/vitest/dist/chunks/coverage.DfSpMS-b.js), withif (process.env.X)truthiness — the workflow's empty value on non-ECS runners keeps defaults, matching the description. - Process census (
harness/04b-worker-count-v2.sh, witnessevidence/05-worker-census.png): same 10-file subset — unbounded arm peaks at 64 vitest node processes (~1 worker per core on this 64-core host: the nightly's failure regime); bounded arm (the lane's four vars) at 5 =node (vitest)main + workers 1-4, peak list recorded. maxForks=4 honored exactly. - Contract pin (from main, shipped at HEAD): deleting the
env:block fromRun Script Teststurnspackage-scripts.test.js > wires release quality checksred quoting the missingVITEST_MAX_THREADSline; restoring turns it green; tree restored clean (logs/05-contract-run.log). Reviewer Test Plan step 3 is executable as written.
Vacuity / mutation matrix (test-only change — does the suite still hold the behavior down?)
Witness: evidence/03-mutation-matrix.png; raw logs logs/04-mutation-retries.log, logs/05-mutation-nokill.log.
| # | mutation (scratch, restored) | expected | observed | verdict |
|---|---|---|---|---|
| M1 | production MAX_UPLOAD_ATTEMPTS 3→2 |
red at attempt-count assertion | red 6.0s: expected … to throw … /after 3 attempts/ but got 'ossutil failed after 2 attempts' (line 215) |
caught |
| M2 | production kill removed (timeout/killSignal dropped) |
red | red 60.2s: expected [Function] to throw an error — without the kill the shim's own 60s keepalive expires and it exits 0, so the upload "succeeds" |
caught |
| M3 | test constant reverted 2000→400 | red under contention | red: ENOENT cell above | caught |
| control | unmutated head | green | 16/16 green under the same amplified contention | green |
Every red quotes the intended behavioral assertion, not an import or fixture break; the positive controls land in the mutated file's own suite.
Mechanistic note from M2 (informational, not a defect): while spawnSync blocks synchronously, vitest's 30s test timeout cannot preempt the worker — the M2 mutant ran 60.2s before failing on the assertion. The explicit kill bound is therefore the only bound on this code path; the 30s timeout is not a safety net for it. As shipped the kill is present, so this is a property to keep in mind, not a finding.
Targeted gate
env VITEST_MAX_THREADS=4 VITEST_MIN_THREADS=1 VITEST_MAX_FORKS=4 VITEST_MIN_FORKS=1 npm run test:scripts (reviewer-plan step 1, lane-shaped): Test Files 3 failed | 72 passed (75), Tests 17 failed | 1961 passed (1978), exit 1, 396s (logs/10-full-suite.log, witness evidence/04-full-suite-summary.png). The deflaked upload-aliyun-oss-assets.test.js passes in-suite: 16 tests, 20.7s. (The PR's "73 files" predates two files main added; 75 collected at this head.)
The 3 failing files are attributed to this verify container, not the diff — A/A at HEAD^1 (logs/12-aa-base.log), head rerun (logs/13-head-rerun.log), and single-command probes (logs/14-env-probe.log), witness evidence/06-suite-attribution.png:
| failing file | head full-suite | base A/A | cause (proven) |
|---|---|---|---|
install-script.test.js |
FAIL (file-level throw) | identical FAIL | zip binary missing on this image with CI=true; the test's own guard throws "zip/unzip missing on a CI host" |
check-tui-dep-direction.test.js (13 tests) |
FAIL EACCES mkdtemp .../.qwen/gate-e2e-* |
passes | .qwen is a root-owned read-only mount in this container (dr-xr-xr-x root); mkdtemp EACCES on the main tree, OK on a writable worktree — the diff never touches that path |
qwen-autofix-workflow.test.js (2 tests) |
FAIL Test timed out in 30000ms |
passes | load flake: green on head rerun and at base; failed only inside the 75-file/4-worker run under load ~195 |
Sibling sweep: grep across scripts/ for sub-second spawn timeouts found none — the 400ms figure in this test was the only spawn-race-prone bound in the tree; other timeout: users sit at 10-15s (5-7.5× the headroom proven sufficient here) and assert on exit status of run-to-completion children, not on first-write log counts.
Findings
None blocking. The two narrative corrections above are about the description, not the code.
Not covered
- Per-commit attribution: checkout is depth 2 — only
HEAD^2reachable locally while$QWEN_VERIFY_CONTEXTlists 3 commits; the aggregateHEAD^1..HEADdiff was verified, individual commits were not (the two superseded ones no longer contribute to the diff). - The actual nightly release run — the PR's own stated absolute proof.
- Reproduction on the original failing SHA of Release Failed for v0.22.3-nightly.20260902.77d41f48d3 on 2026-09-02 #10755: not reachable in this checkout. The reproduction here is of the mechanism (kill racing the shim's first write) on the fixed/unfixed constants, not of the nightly's exact victim set, which also included ESLint/workflow-replay tests whose failures the worker bound (from base) addresses — that bound is verified only to the extent of the premise context above, not re-proven against those suites' timeouts.
- Windows / macOS: the changed test is platform-agnostic in mechanism (spawnSync + tmpdir) but only Linux was exercised; the lane itself is Linux.
- Other
npm run test:ciconsumers (desktop/SDK/vscode-companion pipelines): untouched by the diff, as the description states. - The 3 container-attributed suite failures are reported as environmental with A/A + probe evidence; they would not fail on a runner with
zipinstalled and a writable.qwen.
Methodology
Single round on the merge-ref checkout (HEAD = merge commit, HEAD^1 = base tip, HEAD^2 = PR head = headRefOid). Harnesses in harness/, raw logs in logs/: the ladder drives the real uploadAssets() from scripts/upload-aliyun-oss-assets.js with the test's own shim recipe (no mocks of the unit under test); amplified contention = 20 spinners + taskset pinning to one CPU, matching the commit message's "20-way single-CPU contention"; vitest cells ran the real suite config; mutations were scratch edits with byte-exact restore (git status --porcelain clean after each); the base worktree for A/A lived under tmp/ and is removed after the round. Assertion totals count only scripted checks that executed; expected base-cell failures are encoded as passes. The one probe miscalibration (first worker-census arm counting its own bash/npm/sh wrapper processes, reading 7 where vitest node processes were main+workers) is excluded in assertions.json with its raw log preserved and replaced by the corrected census (04b-worker-count-v2.log).
Flakiness gate log
rounds=5 files=1 skipped=0
file scripts/tests/upload-aliyun-oss-assets.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/upload-aliyun-oss-assets.test.js
per-file results (P=pass F=fail I=infra-exit, one letter per run):
scripts/tests/upload-aliyun-oss-assets.test.js: PPPPP
verdict: pass
summary: 1 changed test file(s) x 5 identical rounds, no divergence
--- per-invocation detail (full copy in the artifact) ---
round 1 · scripts/tests/upload-aliyun-oss-assets.test.js: P (exit 0)
round 2 · scripts/tests/upload-aliyun-oss-assets.test.js: P (exit 0)
round 3 · scripts/tests/upload-aliyun-oss-assets.test.js: P (exit 0)
round 4 · scripts/tests/upload-aliyun-oss-assets.test.js: P (exit 0)
round 5 · scripts/tests/upload-aliyun-oss-assets.test.js: P (exit 0)
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: 🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round: no action takenThis round produced no code change and no commit. The head stays at Feedback triage[ic:5509227034] — The maintainer command that dispatched this round. No reviews and no inline comments were present this round, so there is no Checks: none failed
A maintainer can clear it by re-running that one cancelled job; nothing in the Head healthThe current head already carries green evidence from CI and from a local run
PR shape after the merge with mainWorth restating for a reader arriving now: main's VerificationCommands actually run this round (no source change, so no build/typecheck/lint
No commit was created, no file was modified, and no review thread was resolved. 中文说明Autofix 评审轮次:未采取任何行动本轮没有产生任何代码改动,也没有提交。head 仍停留在 反馈分类[ic:5509227034] — 触发本轮的维护者命令。 本轮没有评审(reviews),也没有行内评论(inline comments),因此没有需要 resolve 的讨论串,也不需要发布 检查状态:没有失败项
维护者可以通过重跑这一个被取消的作业来清除该状态;重试时 diff 中的任何内容都不会表现不同。 head 健康状况当前 head 已经具备来自 CI 以及本轮本地运行的绿色证据:
与 main 合并后的 PR 形态为现在才来看的读者重述一遍:main 上的 验证本轮实际执行的命令(没有源码改动,因此不需要 build/typecheck/lint——CI 已经构建了这个确切的 head):
本轮未创建提交、未修改任何文件、也未 resolve 任何评审讨论串。 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
Code reviewIndependent proposal first: for an attempt-count shortfall caused by kills racing child-process startup under load, the minimal fix is to widen the test's Verified against the code at the reviewed commit:
No blockers, no AGENTS.md violations. Testing (the PR's own CI, fetched via API — no PR code was run in this review)
Sandboxed lanes ( 中文说明代码审查先给出独立方案:对于"负载下 kill 与子进程启动竞态导致尝试次数偏少"的问题,最小修复就是把测试的 在受审提交上逐点核实:
无阻塞问题,无 AGENTS.md 违规。 测试(来自 PR 自身 CI 的 API 数据——本审查未运行任何 PR 代码)CI 表格见上方标记区域。要点:
沙箱验证通道( — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — clean, mechanism-verified one-knob test stabilization; the two nits are the stale title/body (the headline fix was absorbed by #10765) and the unrelated web-shell smoke timeout needing a job re-run. Reflection: this PR had an unusual lifecycle — opened as the full fix for the #10755 nightly failure, then main absorbed the worker-cap half through #10765 while it was in review. The question for this pass is whether the remaining delta still earns its keep, and it does: main still carries the 400 ms kill bound that raced child startup on the loaded pool (observed twice in the reproductions as 1-of-3 and 2-of-3 attempt counts), and nothing else is queued to fix it. The change is exactly what I would have proposed — widen the bound to 2 s, keep it far below the simulated 60 s stall so the kill remains the thing under test, and document the budget math. Every line in the diff is necessary; no drive-bys. Two named nits, neither blocking:
Verdict: approve, pinned to the reviewed commit. 中文说明置信度:4/5 —— 干净、机制已核实的一行参数测试稳定化改动;两个小问题是过时的标题/正文(重头修复已被 #10765 吸收)以及与本改动无关的 web-shell smoke 超时需要重跑。 反思:这个 PR 的生命周期不寻常——作为 #10755 夜间发布失败的完整修复提交,随后在评审期间,worker 上限部分被 #10765 合入 main。本轮要回答的问题是剩余增量是否仍然有价值,答案是肯定的:main 上仍然存在那个在负载资源池上与子进程启动竞态的 400 毫秒 kill 上限(复现中两次观测到 3 次只记录 1 次和 2 次),且没有其他排在队列中的修复。这个改动与我会提出的方案完全一致——把上限放宽到 2 秒,保持远低于模拟的 60 秒挂起使 kill 仍是被测对象,并记录预算计算。diff 中每一行都是必要的;没有顺手改动。 两个不阻塞的小问题:
结论:批准,锚定在受审提交上。 — Qwen Code · qwen3.8-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
Released in v0.23.0. |






What this PR does
Bounds the Vitest worker count in the release workflow's scripts-test lane the same way every sibling lane already does on the shared ECS pool, and pins the bound in the release contract test for both test lanes. It also stabilizes one test in the scripts suite whose per-attempt kill timeout raced child-process spawn latency under pool load: the bound moves from 400 ms to 2 s, which stays far below the simulated 60 s stall, so the timeout-and-retry behavior under test is unchanged.
Why it's needed
The 2026-09-02 nightly release failed on the
qualityaggregate (issue #10755). The failing component is the scripts-test lane that the release-sharding split (#10619) created the day before: it runs the scripts suite with Vitest's default worker count — one worker per host core. On the shared pool runners (64 cores, load average ~120 with concurrent jobs), the suite's tests instantiate repo-wide ESLint engines and replay extracted workflow bash through spawned subprocesses; ~64 parallel workers each spawning heavy children pushed 5–8 tests per run past the suite's 30 s timeout, with a different victim set every run. Reproduced twice on the exact failing SHA: 30 s timeouts across the ESLint boundary guards and workflow replays, plus an attempt-count shortfall in the upload-stall test. Every failing test passes when run without parallel contention. The workspace-test lane and the main CI gate already apply this exact tunable bound on ECS (#10667); the new lane simply never inherited it.Reviewer Test Plan
How to verify
Run the scripts suite the way the fixed lane does on an ECS-shaped environment (a loaded multi-core host makes the difference visible):
env VITEST_MAX_THREADS=4 VITEST_MIN_THREADS=1 VITEST_MAX_FORKS=4 VITEST_MIN_FORKS=1 npm run test:scripts— expected: all 73 test files pass (~5 min). Without the four variables on the same host, expect sporadicTest timed out in 30000msfailures across the ESLint and workflow-replay tests. Confirm the contract witness by deleting the newenv:block from theRun Script Testsstep in the release workflow:npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/package-scripts.test.js -t "wires release quality checks"must fail, and restoring the block must turn it green. The upload-stall test (scripts/tests/upload-aliyun-oss-assets.test.js) should pass with a ~12 s runtime.Evidence (Before & After)
N/A (CI-workflow change, no user-visible behavior)
Tested on
Environment (optional)
Self-hosted Linux runner from the same pool class as the release lanes (64 cores, load average ~120 during verification); unit-test-level verification only, no sandbox.
Risk & Scope
npm run test:ci(desktop/SDK/vscode-companion release pipelines) were not touched; no evidence they fail. The absolute proof is the next nightly release run.QWEN_CI_VITEST_MAX_WORKERSrepository variable.Linked Issues
Fixes #10755
中文说明
本 PR 做了什么
以与同级分支任务在共享 ECS 资源池上完全相同的方式,为发布工作流的脚本测试分支设置 Vitest worker 上限,并在发布契约测试中将该上限同时固定在两个测试分支上。此外还稳定了脚本套件中的一个测试:它的单次尝试 kill 超时与资源池负载下的子进程派生延迟存在竞态,上限从 400 毫秒调整为 2 秒,仍远低于模拟的 60 秒卡死,因此被测试的超时-重试行为保持不变。
为什么需要
2026-09-02 的夜间发布在
quality汇总上失败(issue #10755)。失败的组件是脚本测试分支——它由一天前的发布分片拆分(#10619)新建,以 Vitest 的默认 worker 数(每主机核心一个 worker)运行脚本套件。在共享资源池 runner 上(64 核,因并发任务负载均值约 120),该套件的测试会实例化全仓库 ESLint 引擎,并通过派生子进程回放从工作流中逐字提取的 bash;约 64 个并行 worker 各自派生重量级子进程,导致每次运行有 5–8 个测试突破套件的 30 秒超时,且每次中招的测试集合都不同。已在完全相同的失败 SHA 上复现两次:ESLint 边界守护与工作流回放测试出现 30 秒超时,外加 upload-stall 测试的尝试次数不足。所有失败测试在无并行争用时都能通过。workspace 测试分支和主 CI 门禁早已在 ECS 上应用了这个完全相同的可调节上限(#10667);新分支只是从未继承它。审阅者测试计划
如何验证
按修复后分支的方式运行脚本套件(在负载较高的多核主机上差异最明显):
env VITEST_MAX_THREADS=4 VITEST_MIN_THREADS=1 VITEST_MAX_FORKS=4 VITEST_MIN_FORKS=1 npm run test:scripts— 预期:全部 73 个测试文件通过(约 5 分钟)。在同一主机上不设这四个变量运行,预期出现零散的Test timed out in 30000ms失败,分布在 ESLint 与工作流回放测试中。通过删除发布工作流Run Script Tests步骤中新增的env:块来验证契约见证:npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/package-scripts.test.js -t "wires release quality checks"必须失败,恢复该块后必须变绿。upload-stall 测试(scripts/tests/upload-aliyun-oss-assets.test.js)应通过,耗时约 12 秒。前后对比证据
N/A(CI 工作流改动,无用户可见行为)
测试环境
环境(可选)
与发布分支同资源池类别的自托管 Linux runner(64 核,验证期间负载均值约 120);仅单元测试级别的验证,未使用沙箱。
风险与范围
npm run test:ci的工作流(桌面/SDK/VS Code companion 发布流水线)未改动;没有证据表明它们会失败。最终验证是下一次夜间发布运行。QWEN_CI_VITEST_MAX_WORKERS仓库变量。关联 Issue
Fixes #10755