fix(ci): run the Windows suite in a single fork (the bounded pool was not enough)

The `--maxWorkers=2` fix from the previous commit was falsified by real CI:
run 34574847774 still ended with `[vitest-pool]: Worker forks emitted error /
Worker exited unexpectedly` while reporting 126 of 127 files passed and zero
FAIL lines. The worker that died was the one running
tests/native-surface.spec.ts — it produced NO results at all, which is the
same shape as the two earlier crashes (125/126 and 123/124: N-1 files
reported plus a silent worker exit at teardown).

So the consistent signature across all four crashes is a worker-recycling
race, not a test failure. `test:windows` now runs the whole suite in ONE fork
(`--maxWorkers=1`), which removes recycling entirely; the cost is wall-clock
(14 s -> 41 s locally, and the Windows step stays well inside its 30-minute
budget) and the signal is unchanged. Retries are still deliberately not used —
they would hide a real regression instead of removing the race.

The CI comment now records both Windows failure classes with their evidence,
so the next person can tell what this flag is for.
This commit is contained in:
Menghuan1918
2026-09-11 15:43:22 +08:00
parent fe2017af49
commit 4fe67e3965
3 changed files with 24 additions and 13 deletions
+20 -10
View File
@@ -92,16 +92,26 @@ jobs:
run: pnpm typecheck
- name: Lint
run: pnpm lint
# `test:windows` caps the fork pool at 2 instead of `pnpm test`'s
# unbounded default. Five specs in this suite really start processes
# (tests/agent-pty, pty-deps, pty-helpers, install-powershell,
# smoke): node-pty's ConPTY helper and a cold `powershell.exe` are
# both console-bound, and this 2-core runner made parallel spawning
# the amplifier behind every red `Test` step in the 2026-09-09/10
# window — six real 5000 ms timeouts (agent-pty `:71`/`:106`,
# install-powershell `:105`, the latter measuring 12.1 s) and two
# silent vitest worker deaths. The test bodies now carry their own
# explicit timeouts as well; this just stops them starving each other.
# `test:windows` runs the whole suite in ONE fork (`--maxWorkers=1`),
# not `pnpm test`'s unbounded pool. Two distinct Windows-only failures
# motivated it, both recorded in docs/plans/2026-09-10-dsh-0.1.5-rc.2-adaptation.md:
#
# 1. Six red runs were real 5000 ms vitest timeouts in specs that
# really start a process (tests/agent-pty:71/:106,
# tests/install-powershell:105 — a cold powershell.exe start
# measured 12.1 s, tests/git.spec.ts:85 at 9.6 s). The suite now
# declares its own budgets (global testTimeout 15 s, 30 s in the
# two heaviest describes), and serial execution stops the
# process-spawning specs starving each other on a 2-core runner.
# 2. Four runs died at teardown with `[vitest-pool]: Worker forks
# emitted error / Worker exited unexpectedly` while reporting
# N-1 of N files passed and zero FAIL lines — a worker recycling
# race, not a test failure. It survived --maxWorkers=2, so the
# lane now recycles nothing at all.
#
# No retries: they would hide a real regression instead of removing the
# race. The cost is wall-clock (the Windows step runs several minutes
# against a 30-minute job budget), not signal.
- name: Test
run: pnpm test:windows
- name: Build
@@ -44,7 +44,8 @@ DSH `dsh-v0.1.5-rc.1..dsh-v0.1.5-rc.2`(上游 4 个 commit / 300 个变更文
- 证据:run `34500290681`(295615abb)、`34448911025`(bbf953b8a) 报 `[vitest-pool]: Worker forks emitted error / Caused by: Error: Worker exited unexpectedly`(`Errors 1 error`),同一份日志里 `0 FAIL`、125 个文件全过;后者静默吞掉一个文件的 10 条结果。同一 commit `8ae527170` 的另一次 run 里 `ci-windows` 是**绿的** → 该步骤可证伪地非确定。
- 归因(**推断,非确证**):真起进程的 spec 有 5 个(`agent-pty` / `pty-deps` / `pty-helpers` / `install-powershell` / `smoke`),node-pty 的 ConPTY 辅助进程与冷启 `powershell.exe` 都绑定控制台,在默认**无界 fork 池**下并行抢占;本地 macOS 无法复现该模式,因此不做无证据的猜测性改动(不加 `retry`,不改默认 pool)。
- 修法:`package.json` 新增 `test:windows`(`vitest run --maxWorkers=2`),`ci-windows` 的 `Test` 步骤改跑它——**只在 Windows CI 收敛并发,`pnpm test` 的本地语义不变**。若真实 Windows 结果仍复现,按"升级而非掩盖"的原则继续收敛(同 PR 内串行化这些 spec,仍不加 retry),并在 PR 中如实标注。
- 修法(两次迭代,第一次被真实结果证否):先上 `test:windows`(`vitest run --maxWorkers=2`)——**真实 Windows 上仍复现**(run `34574847774`:126/127 文件全过、`0 FAIL`、`Unhandled Error: Worker exited unexpectedly`,丢结果的那个 worker 正是跑 `tests/native-surface.spec.ts` 的)。该次日志里 `native-surface.spec.ts` **零结果**,与历史两次崩溃同形(`34500290681` 125/126、`34448911025` 123/124——都是「N-1 个文件有结果 + teardown 期 worker 静默退出」)。
- 迭代后的修法:`--maxWorkers=1`(整个套件跑在一个 fork 里,**零 worker 回收**)。理由:跨 4 次崩溃的一致形态是「worker 回收/退出竞态」,而不是某个测试失败;单 worker 从根上取消这个竞态,代价只是墙钟时间(本地 14s → 41s,Windows 步骤仍在 30 分钟 job 预算内)。**仍不加 retry**:retry 会把真实回归一起吞掉。
### C. `plugin-mount` 的 npm 安装失败(4 次:2× ETARGET + 2× OOM)
@@ -84,7 +85,7 @@ DSH `dsh-v0.1.5-rc.1..dsh-v0.1.5-rc.2`(上游 4 个 commit / 300 个变更文
- **真机挂载冒烟(rc.2)**:`DSH_CMD` 指向 `npx -y --package @deepseek-ai/dsh@0.1.5-rc.2 dsh`;见"落地"节的实测计数。
- **真实 Windows 收敛**:`ci-windows` **连续两次全绿**(同一 commit `5451f5c`)——`pull_request` 自动触发的 run [34561189320](https://github.com/omdsh-dev/DSH-better-sidebar/actions/runs/34561189320) 与随后 `workflow_dispatch` 的 run [34561457067](https://github.com/omdsh-dev/DSH-better-sidebar/actions/runs/34561457067),两次都是 **126 files / 1318 passed / 3 skipped / 0 errors**(`Unhandled Errors` 与 `Worker exited unexpectedly` 出现次数均为 **0**;同环境的 2026-09-09/10 窗口里这两行出现过 2 次)。同一分支的 `plugin-mount` 两次都绿:`added 521 packages in 24s`(**0 条 ERESOLVE**,与历史 OOM run 的 3062 条对比)、`test:mount` **7 passed**、聚合双挂载通过。
- **首次真实结果的证伪(已修正)**:本 PR 的第一版修复在同一 commit 上仍红两处,`.github/workflows/ci.yml` 的改动因此被真实证据改过一轮——见 C 节的「一次被真实结果否掉的中间方案」与 A 节第三个文件(`tests/git.spec.ts:85`,9607ms)。
- **B 类的实测结论**:连续两次 Windows 全绿、0 次 worker 死亡,但这**不等于证明了 B 类的根因**(只有 2 次历史样本、无可比对照);记为「并发收敛 + 超时预算到位后未再复现」,若将来复现按 §B 的升级路径继续收敛。
- **B 类的实测结论(已按"升级而非掩盖"走完两轮)**:`--maxWorkers=2` 下仍崩溃(run `34574847774`),故升级为 `--maxWorkers=1`(单 fork、零回收)。这**不等于证明了根因**(4 次崩溃的一致形态是「N-1 文件有结果 + teardown 期静默退出」,只能说明是 worker 回收竞态而非测试失败);收敛判据是单 worker 下连续两次 `ci-windows` 全绿。
- **本地 3080 真机**(DSH 0.1.5-rc.2 + 插件 0.19.1):部署与路由探针已完成(见"落地"节),GUI 人工验证由用户执行。
## 不做
+1 -1
View File
@@ -80,7 +80,7 @@
"watch": "tsdown --watch",
"prepare": "tsdown",
"test": "vitest run",
"test:windows": "vitest run --maxWorkers=2",
"test:windows": "vitest run --maxWorkers=1",
"test:mount": "bash scripts/e2e-mount.sh",
"test:mount:aggregate": "bash scripts/e2e-aggregate-mount.sh",
"clean": "node -e \"require('node:fs').rmSync('lib',{recursive:true,force:true})\"",