--- slug: 01-10-vitest-build-test-timeout status: resolved goal: find_and_fix trigger: "npm test (full run, no SKIP_BUILD=1) fails 1/150 with timeout in tests/background/no-test-hooks-in-prod-bundle.test.ts at line 247 — Test timed out in 5000ms" phase: 01-stabilize-video-pipeline plan: 01-10 opened: 2026-05-20 closed: 2026-05-20 orchestrator_diagnosed: true --- # Debug session 01-10-vitest-build-test-timeout — vitest it() default 5s ceiling races slower welcome-page build ## Problem statement After Plan 01-10 closure (commits d48a715 welcome mark + 4bba679 notif-text), `npm test` (full run, no `SKIP_BUILD=1`) failed 1/150 with a vitest timeout in `tests/background/no-test-hooks-in-prod-bundle.test.ts` at line 247: ``` FAIL tests/background/no-test-hooks-in-prod-bundle.test.ts > production bundle has no test-hook leaks (Tier-1 gate — T-1-11-01) > npm run build completes and dist/ exists with at least one chunk Error: Test timed out in 5000ms. ``` Standalone `npm run build` had slowed from ~2.88s (pre-Plan-01-10) to ~5.28s due to welcome page Vite processing + SVG `?url` import + 8 WOFF2 fonts shipped in d48a715 / 49f087f. With the build now exceeding 5s, vitest's default 5000ms `it()` ceiling races and loses. `SKIP_BUILD=1 npm test` was passing 150/150 GREEN, confirming this was purely a test-infrastructure timeout-tuning issue, NOT a real build/grep-gate regression. ## Root cause The `it()` block at `tests/background/no-test-hooks-in-prod-bundle.test.ts:247` was declared without a 3rd-arg timeout option: ```ts it('npm run build completes and dist/ exists with at least one chunk', async () => { if (process.env.SKIP_BUILD !== '1') { await runProductionBuild(); } // ... }); // <-- no timeout 3rd arg → vitest's 5000ms default applies ``` The test author had correctly bounded the EXEC-level child-process timeout via `BUILD_TIMEOUT_MS = 60_000` at line 240 (passed to `execFileAsync`), but **forgot to bound the surrounding `it()` block**. Vitest's default it() timeout is 5000ms; the build now takes ~5.28s, so the it() ceiling fires before the exec bound is even close. This is the classic "two-tier timeout where only one tier is configured" bug. The exec timeout existed but was useless because the outer it() timeout fired first. ## Fix design Surgical one-line change: add `, 30_000` as the 3rd arg to the failing `it()` call. 30 seconds was chosen because: - Generously above the observed 5.28s build + npm overhead (~6× headroom) - Well below the 60s exec bound (`BUILD_TIMEOUT_MS`), so the exec timeout remains the dominant ceiling for true hangs - Above the 15s minimum requested by the orchestrator - Consistent with vitest convention for build-touching it() blocks (real-IO tests routinely declare 10s+ ceilings) Inline comment added above the `it()` documenting why the 30s ceiling exists, citing the +2.4s welcome-page-asset slowdown and the relationship to `BUILD_TIMEOUT_MS`. The alternative — setting a global `testTimeout` in `vitest.config.ts` — was rejected because: - 95% of vitest cases are pure-CPU and should keep the 5s default (catches accidental hangs fast) - Only this one it() touches IO at build scale - Per-it() timeouts are the standard vitest idiom for "this specific test does slow IO" `SKIP_BUILD=1` env-var escape hatch left untouched for CI environments. ## Files modified - `tests/background/no-test-hooks-in-prod-bundle.test.ts` (line 247 it() — added `, 30_000` 3rd arg + 7-line explanatory comment above) ## Acceptance gates — all PASS - `npm test` (FULL run, no SKIP_BUILD=1): **150/150 GREEN**, exit 0, 12.89s total - `npx tsc --noEmit`: exit 0 - `npm run build`: exit 0, 4.86s (within new 30s it() ceiling with ~6× margin) - Tier-1 grep gate: PASS (all 12 FORBIDDEN_HOOK_STRINGS asserted against `dist/`, including the build-completes gate that was previously timing out — now passes within the new ceiling) ## Noteworthy - **The exec-level `BUILD_TIMEOUT_MS = 60_000` ceiling still bounds the child process.** This fix only adjusts the SURROUNDING vitest it() ceiling so the exec bound becomes reachable. If `npm run build` ever truly hangs, the exec timeout fires at 60s; the it() ceiling fires at 30s. Both bounds remain active and meaningful. - **No SKIP_BUILD logic touched.** CI environments using `SKIP_BUILD=1` (with a pre-existing dist/) skip the slow path entirely and are unaffected. - **Build slowdown attribution** (for future archaeology): - d48a715: welcome page mokosh-mark.svg via `?url` import (+~0.4s Vite asset processing) - 49f087f: welcome HTML/CSS/JS entries + 8 WOFF2 font assets (+~2.0s emit + manifest) - Net: ~2.88s → ~5.28s on this hardware