Repository navigation
Crash in V8 GC on v24.x and earlier #62393
Description
Activity
As per https://issues.chromium.org/issues/483731079, this is a V8 bug, the fix for which is present in Node 25.0.0 or later. Not sure whether this is the kind of thing maintainers would cherry pick a fix for, into an LTS line.
- addedv8 engineIssues and PRs related to the V8 dependency.Issues and PRs related to the V8 dependency.v24.xIssues that can be reproduced on v24.x or PRs targeting the v24.x-staging branch.Issues that can be reproduced on v24.x or PRs targeting the v24.x-staging branch.
on Mar 27, 2026 I've got a reliable reproduction of this issue in this repo: ChrisRogers-TheNBS/jest-segfault-repro.
I've not observed the segfault in v25 at all, so does indeed appear to be fixed! Would be nice to have a fix in the LTS, but I guess v26 is releasing soon anyway.
For a quick workaround to still use v24, invoke node directly with the
--no-sparkplugflag i.e.node --no-sparkplug ./node_modules/.bin/jest
instead of the usual
npx jestornpm run testif you have jest configured in there.Reacted by Lance Jeffers- added 2 commits that reference this issue
on Apr 26, 2026 We've encountered what appears to be the same issue. A demangled call stack, in case it helps anyone else who's searching for this issue:
v8::internal::ClearStaleLeftTrimmedPointerVisitor::VisitRootPointers(v8::internal::Root, char const*, v8::internal::FullObjectSlot, v8::internal::FullObjectSlot) + 100 v8::internal::InternalFrame::Iterate(v8::internal::RootVisitor*) const + 240 v8::internal::Isolate::Iterate(v8::internal::RootVisitor*, v8::internal::ThreadLocalTop*) + 364 v8::internal::Heap::IterateRoots(v8::internal::RootVisitor*, v8::base::EnumSet<...>, v8::internal::Heap::IterateRootsMode) + 460 v8::internal::MarkCompactCollector::MarkRoots(v8::internal::RootVisitor*) + 56 v8::internal::MarkCompactCollector::MarkLiveObjects() + 968 v8::internal::MarkCompactCollector::CollectGarbage() + 128 v8::internal::Heap::MarkCompact() + 420 v8::internal::Heap::PerformGarbageCollection(...) + 824 v8::internal::Heap::CollectGarbage(...)::$_1::operator()() const + 1188 heap::base::Stack::SetMarkerAndCallbackImpl<...>(heap::base::Stack*, void*, void const*) + 40 PushAllRegistersAndIterateStack + 40 <-- conservative stack scan v8::internal::Heap::CollectGarbage(...) + 748 v8::internal::StackGuard::HandleInterrupts(v8::internal::StackGuard::InterruptLevel) + 504 v8::internal::Runtime_StackGuardWithGap(int, unsigned long*, v8::internal::Isolate*) + 312 Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit + 84 ... Builtins_AsyncFunctionAwaitResolveClosure + 72 Builtins_PromiseFulfillReactionJob + 56 Builtins_RunMicrotasks + 560 v8::internal::MicrotaskQueue::RunMicrotasks(...) + 356 v8::internal::MicrotaskQueue::PerformCheckpoint(v8::Isolate*) + 132 ... node::StreamBase::CallJSOnreadMethod(...) + 232 <-- worker IPC stream read node::EmitToJSStreamListener::OnStreamRead(...) + 372 node::LibuvStreamWrap::OnUvRead(...) + 660 uv__stream_io + 1332 uv__io_poll + 1032 uv_run + 408 node::SpinEventLoopInternal(node::Environment*) + 248Reacted by Lance JeffersAnother data point from a different worker model, plus a version matrix — and an answer to @isker's LTS question.
tl;dr: 24.18.0 (current 24 LTS) still reproduces. 20 and 22 do not.
Same crash, frame-for-frame, same
SIGSEGVat address0xe.Version matrix — macOS 15 arm64, same machine, same commit, repeating one Jest suite (379 suites / ~2180 tests) end to end. Each configuration independently corroborated by counting new
~/Library/Logs/DiagnosticReports/node-*.ipsreports:Node V8 runs with a SIGSEGV worker 20.20.2 11.3 0 / 50 22.23.1 12.4 0 / 40 24.13.0 13.6.233.17-node.37 4 / 26 24.18.0 13.6.233.17-node.50 1 / 25 24.18.0 + --no-sparkplug13.6.233.17-node.50 0 / 40 The title says "v24.x and earlier" — we could not reproduce on 20 or 22 in 90 combined runs, so for this signature it looks specific to V8 13.6.
Re @isker's backport question: 24.18.0, the current 24.x LTS, still reproduces, so the 24 line does still need the cherry-pick.
Different worker model, same crash. The OP mentions worker threads + vm. We don't use worker threads — jest-worker's default
ChildProcessWorkerforks child processes. Jest does usevmto execute test files. So across these reports the common factor looks likevm+ Sparkplug rather than worker threads.--no-sparkplug(thanks @ChrisRogers-TheNBS) works for us: 0/40, with no measurable wall-time cost (12.0–12.8s flagged vs 10.4–15.4s unflagged — the flagged runs sit inside the unflagged runs' own variance).For the record,
--no-move-object-startdoes not help (3/40). I tried it because of theLeftTrimmedsymbol name, and verified viaprocess.execArgvinside the workers that the flag was actually applied before ruling it out.One packaging note for anyone applying the workaround in a pnpm monorepo: neither
--no-sparkplugnor--no-move-object-startis permitted inNODE_OPTIONS, and.bin/jestis a shell shim that cannot take node flags, so the invocation has to benode --no-sparkplug node_modules/jest/bin/jest.js. The workers inherit it because jest-worker forks withexecArgv.Reacted by ChrisRogers-TheNBS- added a commit that references this issue
on Aug 5, 2026 - added a commit that references this issue
on Aug 13, 2026 - added a commit that references this issue
on Aug 14, 2026 Still reproduces on v26.7.0 (macOS arm64).
Adding a data point, since the thread currently reads as "fixed in 25.0.0 or later" — that does not match what we see on the current release line.
Same signature as the reports above:
SIGSEGV,KERN_INVALID_ADDRESS at 0x000000000000000e, identical top frames.v8::internal::ClearStaleLeftTrimmedPointerVisitor::VisitRootPointers(...) v8::internal::InternalFrame::Iterate(v8::internal::RootVisitor*) const v8::internal::Isolate::Iterate(v8::internal::RootVisitor*, v8::internal::ThreadLocalTop*) v8::internal::Heap::IterateRoots(...) v8::internal::MarkCompactCollector::MarkRoots(v8::internal::RootVisitor*) v8::internal::MarkCompactCollector::MarkLiveObjects()It surfaces as Jest losing a worker, taking an arbitrary suite down with it — a different suite on every run:
A jest worker process (pid=...) was terminated by another process: signal=SIGSEGV, exitCode=null.Version matrix
Same machine (macOS 26.5.2, arm64), same commit, same command. One full run per data point over ~510 suites / ~6,400 tests. Jest 30 with the default
ChildProcessWorker(no worker threads), TypeScript transformed by@swc/jest,maxWorkers: 4.Node runs with a SIGSEGV worker 24.19.0 3 / 6 26.7.0 3 / 4 24.19.0 + --no-sparkplug0 / 6 26.7.0 + --no-sparkplug0 / 4 So the 24 → 26 move did not help here, and
--no-sparkplugremains effective on both lines.This is a smaller sample than @MichalKantor's, but the unflagged rate is high enough (3/4 on 26.7.0) that it seems worth re-checking before treating the current line as fixed. It is possible the earlier "not observed in v25" report was simply under-powered — at the rates seen in this thread, a handful of clean runs is not unusual.
Cost of the workaround
Wall-clock for the same suite on 26.7.0: 36.3–37.2 s unflagged vs 35.7–40.3 s with
--no-sparkplug. The ranges overlap; whatever the difference is, it is within run-to-run variance for us.Packaging note
Echoing @MichalKantor: the flag is not permitted in
NODE_OPTIONS, andnode_modules/.bin/jestis a shell shim that cannot take node flags, so the entry point has to be invoked directly. Workers inherit it throughexecArgv:{ "scripts": { "test": "node --no-sparkplug node_modules/jest/bin/jest.js" } }2 remaining items
- added a commit that references this issue
on Sep 14, 2026 - added a commit that references this issue
on Sep 18, 2026 I can reproduce a crash with the same native stack and fault address (
0xe) on macOS ARM64, including with Node.js 24.21.0. Disabling Sparkplug avoids the crash in the runs tested so far.Environment
- macOS 27.0, arm64.
- Node.js 24.15.0: V8
13.6.233.17-node.48. - Node.js 24.20.0 and 24.21.0: V8
13.6.233.17-node.53. - Jest
30.4.2,@typescript-eslint/parser8.62.0, TypeScript6.0.3. - The 24.20.0 and 24.21.0 binaries were downloaded from nodejs.org and verified against the published SHA-256 checksums.
Native crash signature
The macOS crash reports consistently show
EXC_BAD_ACCESS / SIGSEGV, withKERN_INVALID_ADDRESS at 0x000000000000000e:v8::internal::ClearStaleLeftTrimmedPointerVisitor::VisitRootPointers v8::internal::InternalFrame::Iterate v8::internal::Isolate::Iterate v8::internal::Heap::IterateRoots v8::internal::MarkCompactCollector::MarkRoots v8::internal::MarkCompactCollector::MarkLiveObjects v8::internal::MarkCompactCollector::CollectGarbage ... v8::internal::Runtime_StackGuardWithGap Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit Builtins_BaselineOutOfLinePrologueReduced reproduction
I reduced the original application test suite to 64 identical Jest test files, each importing
@typescript-eslint/parserand parsing one TypeScript statement. No application code is loaded, Jest transforms are disabled, and the crash occurs with--runInBand, so parallel Jest workers are not required. No explicit GC calls or--expose-gcare used.The following generator produces the reduced case. It resolves the parser from an existing dependency installation containing the versions above; the generated test files can live in a separate temporary directory. This was tested against an existing workspace dependency installation, not a fresh three-package install, so the complete transitive dependency graph is not pinned by this snippet.
Save this as
generate.cjsin an empty directory:const fs = require('node:fs'); const path = require('node:path'); const { createRequire } = require('node:module'); const dependencyRequire = createRequire( path.resolve(process.argv[2], 'package.json'), ); const parserPath = dependencyRequire.resolve('@typescript-eslint/parser'); const source = `const parser = require(${JSON.stringify(parserPath)}); test('parses TypeScript', () => { const ast = parser.parse('export const result: number = 1;', { sourceType: 'module', }); expect(ast.type).toBe('Program'); }); `; Array.from({ length: 64 }, (_, index) => { const fileName = `parser-${String(index).padStart(2, '0')}.test.js`; fs.writeFileSync(path.join(__dirname, fileName), source); }); fs.writeFileSync(path.join(__dirname, 'jest.config.json'), JSON.stringify({ rootDir: __dirname, testEnvironment: 'node', transform: {}, }, null, 2));
Run with the desired Node version, replacing the two paths below.
DEPS_ROOTis the directory containing the dependency installation'spackage.jsonandnode_modules:DEPS_ROOT=/path/to/dependency-installation REPRO_ROOT=/path/to/empty-directory-containing-generate.cjs node "$REPRO_ROOT/generate.cjs" "$DEPS_ROOT" # May crash with SIGSEGV; repeat if a run completes successfully. node "$DEPS_ROOT/node_modules/jest/bin/jest.js" \ --config "$REPRO_ROOT/jest.config.json" --runInBand # Same tests, with Sparkplug disabled. node --no-sparkplug "$DEPS_ROOT/node_modules/jest/bin/jest.js" \ --config "$REPRO_ROOT/jest.config.json" --runInBand
Results for the reduced case
Node version Flags Result 24.15.0 Default SIGSEGV in 4/4 runs, including one outside the execution sandbox 24.15.0 --no-sparkplugAll 64 tests passed in 3/3 runs 24.20.0 Default SIGSEGV in one run, same native stack and fault address 24.21.0 Default SIGSEGV observed, same native stack and fault address; a later validation run passed 24.21.0 --no-sparkplugAll 64 tests passed in both runs 26.7.0 Default All 64 tests passed in one run Additional observations from the original 937-test suite:
- Node 24.15.0 also crashed with
--no-maglev. - The suite passed on Node 24.15.0 with
--no-sparkplug,--no-opt, or--jitless. - It passed on Node 22.23.2 and 26.7.0 with default runtime flags.
- Node 24.20.0 passed the original suite once but still crashed on the reduced case, so a single successful run should not be taken as evidence that this is fixed.
The matching native stack, fault address, and flag comparisons suggest this may be the same issue, with the Sparkplug/GC path worth investigating. They do not establish the exact V8 defect, and the passing runs do not prove that other Node versions are unaffected. A separate attempt using TypeScript directly in
vmcontexts without Jest did not reproduce the crash; I do not yet have a pure Node/V8 reproducer.Does this fit the issue being tracked here, or would you prefer a separate linked report?
Another data point for this, from a different environment. Same faulting frame, and it is still present on v24.16.0 (this issue names v24.10.0 and earlier). Environment - Node v24.16.0, macOS 15.6.1 (arm64), 8 cores - Jest 30.4.1 running with its default worker count (7 workers) - Crashes are always in a worker process; the parent has crashed this way once Crash 19 macOS crash reports (~/Library/Logs/DiagnosticReports/node-*.ips) collected over 8 days. 16 of them share one faulting frame, byte for byte: v8::internal::ClearStaleLeftTrimmedPointerVisitor::VisitRootPointers v8::internal::InternalFrame::Iterate v8::internal::Isolate::Iterate v8::internal::Heap::IterateRoots v8::internal::MarkCompactCollector::MarkRoots v8::internal::MarkCompactCollector::MarkLiveObjects v8::internal::MarkCompactCollector::CollectGarbage v8::internal::Heap::MarkCompact v8::internal::Heap::PerformGarbageCollection heap::base::Stack::SetMarkerAndCallbackImpl<...> PushAllRegistersAndIterateStack EXC_BAD_ACCESS / SIGSEGV, KERN_INVALID_ADDRESS at 0x6 (12 reports) or 0xe (4 reports). The remaining 3 reports are __kill frames from the parent terminating the already-dead process; one of them is timestamped in the same second as a V8 report, so they look like companions rather than a separate failure. Rate Over a recorded window of 141 test runs with workers: 11 runs crashed this way, about 8%. Over 144 runs of a separate single-process (--runInBand) configuration in the same period: 0. That comparison is confounded — the two configurations also run different test files — and single-process is not immune: the one parent-process crash above happened in the single-process configuration. What I could not establish - No minimal reproducer. The crashes land in unrelated test files (one file has crashed 3 times, another twice, the rest once each), which is what made it look like a flake rather than one bug. - No correlation with memory: free memory at crash time ranged 55%-81%, swap 4.1-6.2 GB. - I have not tested whether any V8 flag avoids it, and I have no evidence either way about whether a later Node release fixes it. Happy to pull specific fields out of the .ips reports if any of it would help.- added a commit that references this issue
on Sep 26, 2026 - added a commit that references this issue
on Sep 29, 2026 - added 5 commits that reference this issue
on Sep 30, 2026 Still reproducing on v24.21.0 (the current Latest LTS), which is after #65753 was merged into
v24.x-stagingbut before any 24.x release carries it. Posting because the original report notes it isn't trivially reproducible — this workload hits it in roughly a third of runs, and--no-sparkplugremoves it completely, which lines up with theBaselineOutOfLineProloguediagnosis.Environment: macOS 26.6.2 (25G83), Apple M5, arm64.
Workload: a jest 30.5.2 suite (25 files, 260 tests) exercising
@typescript-eslint/rule-tester— ESLint rule tests, so heavy AST allocation and churn. Noworker_threadsand no directvmuse in the test code, though jest's own runtime compiles modules throughvm. Default child-process workers.Rates, same checkout, same machine, back to back:
Node V8 invocation result 22.23.3 12.4.254.21-node.57 default 8/8 clean 24.14.0 13.6.233.17-node.41 default crashes, exit 139, zero output 24.14.0 13.6.233.17-node.41 --runInBandcrashes, main process 24.21.0 13.6.233.17-node.53 default 3/10 runs crash 24.21.0 13.6.233.17-node.53 --no-sparkplug16/16 clean 24.21.0 13.6.233.17-node.53 --runInBand10/10 clean 26.10.0 14.6.202.34-node.34 default 6/6 clean Two things worth noting. On 24.14 the main process dies, so the run exits 139 with no output at all; on 24.21 only the workers die, so jest catches it and reports
Test suite failed to run … signal=SIGSEGVand exits 1 with suites missing. Same crash, but on 24.21 it reads as a failing test suite rather than an interpreter crash, which is a good deal harder to attribute. And--no-sparkplughas to be passed to the binary directly —NODE_OPTIONS=--no-sparkplugis rejected with--no-sparkplug is not allowed in NODE_OPTIONS.Faulting stack from
~/Library/Logs/DiagnosticReports, identical to the one in the report,EXC_BAD_ACCESS/KERN_INVALID_ADDRESSat0x6and0xeacross different crashes:v8::internal::ClearStaleLeftTrimmedPointerVisitor::VisitRootPointers(...) v8::internal::InternalFrame::Iterate(v8::internal::RootVisitor*) const v8::internal::Isolate::Iterate(v8::internal::RootVisitor*, v8::internal::ThreadLocalTop*) v8::internal::Heap::IterateRoots(...) v8::internal::MarkCompactCollector::MarkRoots(v8::internal::RootVisitor*) v8::internal::MarkCompactCollector::MarkLiveObjects() v8::internal::MarkCompactCollector::CollectGarbage() v8::internal::Heap::MarkCompact() v8::internal::Heap::PerformGarbageCollection(...) v8::internal::Heap::CollectGarbage(...) heap::base::Stack::SetMarkerAndCallbackImpl<...>(...) PushAllRegistersAndIterateStack v8::internal::Heap::CollectGarbage(...) v8::internal::StackGuard::HandleInterrupts(v8::internal::StackGuard::InterruptLevel) v8::internal::Runtime_StackGuardWithGap(int, unsigned long*, v8::internal::Isolate*) Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit Builtins_BaselineOutOfLinePrologue Builtins_InterpreterEntryTrampolineHappy to run anything against this workload if a 24.x build with the backport would be useful to validate before release.
- added a commit that references this issue
on Oct 4, 2026
Version
24.10.0
Platform
Subsystem
GC, from the looks of it, maybe vm
What steps will reproduce the bug?
It's unfortunately not trivially reproducible. We see this ~1/10 times running our un-cached build runner, primarily while executing tests (which use a combination of worker threads and the vm module)
How often does it reproduce? Is there a required condition?
As stated above, something like 1/10 times.
What is the expected behavior? Why is that the expected behavior?
No segfault.
What do you see instead?
Additional information
I'm not using native extensions other than the segfault handler installed specifically to capture this trace—the segfault was occuring prior to that.