#11291·vitest

threads pool worker isolate SIGSEGVs in V8 GC on large suites (vite 8 rolldown, node 24/26, macOS arm64)

Author: MearmanCreated Sep 16, 2026Updated Sep 16, 2026
Labelspending triage

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:

javascript
// 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::DeinitHeap::StartTearDownCppHeap::StartDetachingIsolateHeap::CollectGarbageGlobalHandles::InvokeFirstPassWeakCallbacks → SIGSEGV (worker teardown GC), or
  • IncrementalMarkingJob::Task::RunInternalMarkCompactCollector::StartMarkingMarkingWorklists::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' (https://github.com/stryker-mutator/stryker-js/issues/6223) — but the repro above has no Stryker involved.

Happy to run instrumented builds or test candidate fixes against the repro if that helps.