Describe the bug
Running a large test suite (454 files, ~4.9k tests) through the Node API with pool: 'threads' and maxWorkers: 1 reliably crashes the process with SIGSEGV during V8 GC on the threads-pool worker isolate. The same file set runs clean under pool: 'forks' (both via the CLI and via the Node API).
Reproduction
Against https://github.com/novus-power/hive (public), on a fresh clone with pnpm install:
// repro.mjs, run with `node repro.mjs` from the repo root
import { createVitest } from "vitest/node";
import fs from "node:fs";
process.env.NODE_ENV = "test";
process.env.VITEST = "1";
const report = JSON.parse(fs.readFileSync("related.json", "utf8")); // testResults[].name of the 454 related files, see below
const files = report.testResults.map((t) => t.name.replace(process.cwd() + "/", ""));
const ctx = await createVitest("test", {
config: "vitest.config.ts",
pool: "threads",
maxWorkers: 1,
coverage: { enabled: false },
watch: false,
bail: 1,
});
const specs = await ctx.globTestSpecifications(files);
await ctx.runTestSpecifications(specs);
await ctx.close();
related.json is just npx vitest related --run --reporter=json --outputFile=related.json core/graph/d1/src/*.ts (excluding test files) — the related set of that package's sources. We couldn't shrink it to a synthetic repro: a generated 420-file suite with real better-sqlite3 and PGlite usage per test does not crash; the real repo's module graph and test weight appear to be what pushes the worker isolate into the crashing GC window.
Expected behaviour
No SIGSEGV; or at worst the same behaviour forks pool exhibits (clean run).
Environment
- Vitest 4.1.10, vite 8.1.4 (rolldown)
- node 24.19.0 and node 26.8.1, both reproduce
- macOS 15 arm64 (also seen on GitHub Actions arm64 macOS runners)
Additional context
Crash signature from macOS DiagnosticReports, consistent across every occurrence (~15 crash reports on this machine alone, since these runs are part of our CI): faulting thread is the pool's WorkerThread, stack terminates in either
v8::internal::Isolate::Deinit → Heap::StartTearDown → CppHeap::StartDetachingIsolate → Heap::CollectGarbage → GlobalHandles::InvokeFirstPassWeakCallbacks → SIGSEGV (worker teardown GC), or
IncrementalMarkingJob::Task::RunInternal → MarkCompactCollector::StartMarking → MarkingWorklists::Local::~Local → SIGSEGV (near-null deref, 0x89).
The crashing process hosts 18 rolldown-worker native threads (vite 8 transforms) alongside the worker isolate; only .node addons loaded are rolldown-binding.darwin-arm64.node and fsevents.node. This is why we originally hit it through StrykerJS — its vitest runner forces pool: 'threads' (stryker-mutator/stryker-js#6223) — but the repro above has no Stryker involved.
Happy to run instrumented builds or test candidate fixes against the repro if that helps.
Describe the bug
Running a large test suite (454 files, ~4.9k tests) through the Node API with
pool: 'threads'andmaxWorkers: 1reliably crashes the process with SIGSEGV during V8 GC on the threads-pool worker isolate. The same file set runs clean underpool: 'forks'(both via the CLI and via the Node API).Reproduction
Against https://github.com/novus-power/hive (public), on a fresh clone with
pnpm install:related.jsonis justnpx vitest related --run --reporter=json --outputFile=related.json core/graph/d1/src/*.ts(excluding test files) — the related set of that package's sources. We couldn't shrink it to a synthetic repro: a generated 420-file suite with real better-sqlite3 and PGlite usage per test does not crash; the real repo's module graph and test weight appear to be what pushes the worker isolate into the crashing GC window.Expected behaviour
No SIGSEGV; or at worst the same behaviour forks pool exhibits (clean run).
Environment
Additional context
Crash signature from macOS DiagnosticReports, consistent across every occurrence (~15 crash reports on this machine alone, since these runs are part of our CI): faulting thread is the pool's
WorkerThread, stack terminates in eitherv8::internal::Isolate::Deinit→Heap::StartTearDown→CppHeap::StartDetachingIsolate→Heap::CollectGarbage→GlobalHandles::InvokeFirstPassWeakCallbacks→ SIGSEGV (worker teardown GC), orIncrementalMarkingJob::Task::RunInternal→MarkCompactCollector::StartMarking→MarkingWorklists::Local::~Local→ SIGSEGV (near-null deref, 0x89).The crashing process hosts 18
rolldown-workernative threads (vite 8 transforms) alongside the worker isolate; only.nodeaddons loaded arerolldown-binding.darwin-arm64.nodeandfsevents.node. This is why we originally hit it through StrykerJS — its vitest runner forcespool: 'threads'(stryker-mutator/stryker-js#6223) — but the repro above has no Stryker involved.Happy to run instrumented builds or test candidate fixes against the repro if that helps.