From bb0c59209b3ef80e249f564081f0cfcce690318d Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Sun, 21 Jun 2026 11:40:06 +0000 Subject: [PATCH] test(cli): pre-set NODE_OPTIONS heap cap so ensureHeap can't re-exec (#2264 CI) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause of the ubuntu-coverage-only failure (3/3 CI runs, passing locally): analyzeCommand calls ensureHeap() (analyze.ts:715), which RE-EXECS the process — spawning `node ` with vitest's argv — unless NODE_OPTIONS already carries --max-old-space-size (analyze.ts:498). That re-exec killed the forked vitest worker ("Worker exited unexpectedly" → the assertion never ran). It only reproduced on the memory-constrained CI runner because locally a high V8 heap-size-limit also short-circuits ensureHeap (analyze.ts:501). Reproduced locally with NODE_OPTIONS="--no-warnings" (no heap cap) → same failure; fixed by pre-setting --max-old-space-size in beforeAll (restored in afterAll), the same workaround cli-e2e uses. Verified: passes under the repro condition, normally, and with --coverage. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01JBJomjoTdBV2eveDVq4JMm --- .../test/unit/analyze-finalize-failure-exits.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gitnexus/test/unit/analyze-finalize-failure-exits.test.ts b/gitnexus/test/unit/analyze-finalize-failure-exits.test.ts index 3bd2364a4..f3aabdcba 100644 --- a/gitnexus/test/unit/analyze-finalize-failure-exits.test.ts +++ b/gitnexus/test/unit/analyze-finalize-failure-exits.test.ts @@ -85,8 +85,18 @@ describe('analyzeCommand — finalize-failure must terminate, not hang (#2264 P1 const baselineUnhandled = process.listeners('unhandledRejection'); const baselineUncaught = process.listeners('uncaughtException'); let exitSpy: MockInstance; + let savedNodeOptions: string | undefined; beforeAll(() => { + // analyzeCommand calls ensureHeap(), which RE-EXECS the process — spawning + // `node ` where argv is vitest's, killing the forked + // worker — UNLESS NODE_OPTIONS already carries a heap cap (analyze.ts:498). + // Locally a high V8 heap-size-limit also short-circuits it (analyze.ts:501), + // which is why this only crashed on the memory-constrained CI runner. Pre-set + // the cap so ensureHeap returns early — the same workaround cli-e2e uses + // (#2264 CI). Restored in afterAll so a reused worker's later files are clean. + savedNodeOptions = process.env.NODE_OPTIONS; + process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ''} --max-old-space-size=8192`.trim(); // Mock process.exit for the WHOLE file — a fatal handler firing between tests // (after a per-test spy would have been restored) can't really exit. exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never); @@ -105,6 +115,7 @@ describe('analyzeCommand — finalize-failure must terminate, not hang (#2264 P1 .filter((l) => !baselineUncaught.includes(l)) .forEach((l) => process.removeListener('uncaughtException', l)); exitSpy.mockRestore(); + process.env.NODE_OPTIONS = savedNodeOptions ?? ''; process.exitCode = 0; });