Skip to content

Crash in V8 GC on v24.x and earlier #62393

Description

@marcusdarmstrong

Version

24.10.0

Platform

Darwin HQX-LGM9L426J5 25.2.0 Darwin Kernel Version 25.2.0: Tue Nov 18 21:09:40 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6000 arm64

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?

PID 22412 received SIGSEGV for address: 0xe
0   segfault-handler.node               0x0000000109630ee4 _ZL16segfault_handleriP9__siginfoPv + 288
1   libsystem_platform.dylib            0x0000000181a67744 _sigtramp + 56
2   node                                0x00000001029e0280 _ZN2v88internal35ClearStaleLeftTrimmedPointerVisitor17VisitRootPointersENS0_4RootEPKcNS0_14FullObjectSlotES5_ + 80
3   node                                0x00000001029278e4 _ZNK2v88internal13InternalFrame7IterateEPNS0_11RootVisitorE + 240
4   node                                0x000000010292cbf8 _ZN2v88internal7Isolate7IterateEPNS0_11RootVisitorEPNS0_14ThreadLocalTopE + 364
5   node                                0x00000001029e04ac _ZN2v88internal4Heap12IterateRootsEPNS0_11RootVisitorENS_4base7EnumSetINS0_8SkipRootEiEENS1_16IterateRootsModeE + 460
6   node                                0x00000001029fefe4 _ZN2v88internal20MarkCompactCollector9MarkRootsEPNS0_11RootVisitorE + 56
7   node                                0x00000001029fa96c _ZN2v88internal20MarkCompactCollector15MarkLiveObjectsEv + 968
8   node                                0x00000001029fa514 _ZN2v88internal20MarkCompactCollector14CollectGarbageEv + 128
9   node                                0x00000001029d94e8 _ZN2v88internal4Heap11MarkCompactEv + 420
10  node                                0x00000001029d8e50 _ZN2v88internal4Heap24PerformGarbageCollectionENS0_16GarbageCollectorENS0_23GarbageCollectionReasonEPKc + 824
11  node                                0x00000001029eb8c4 _ZZN2v88internal4Heap14CollectGarbageENS0_15AllocationSpaceENS0_23GarbageCollectionReasonENS_15GCCallbackFlagsEENK3$_1clEv + 1188
12  node                                0x00000001029eb408 _ZN4heap4base5Stack24SetMarkerAndCallbackImplIZN2v88internal4Heap14CollectGarbageENS4_15AllocationSpaceENS4_23GarbageCollectionReasonENS3_15GCCallbackFlagsEE3$_1EEvPS1_PvPKv + 40
13  node                                0x00000001033a09e4 PushAllRegistersAndIterateStack + 40
14  node                                0x00000001029d5248 _ZN2v88internal4Heap14CollectGarbageENS0_15AllocationSpaceENS0_23GarbageCollectionReasonENS_15GCCallbackFlagsE + 748
15  node                                0x000000010294d124 _ZN2v88internal10StackGuard16HandleInterruptsENS1_14InterruptLevelE + 504
16  node                                0x0000000102e3a19c _ZN2v88internal25Runtime_StackGuardWithGapEiPmPNS0_7IsolateE + 312
17  node                                0x0000000103491f74 Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit + 84
18  node                                0x00000001033f5934 Builtins_BaselineOutOfLinePrologue + 116
19  ???                                 0x00000003205419b8 0x0 + 13427284408
20  ???                                 0x0000000320541bc0 0x0 + 13427284928
21  ???                                 0x00000003206e4504 0x0 + 13428999428
22  node                                0x00000001033f4bec Builtins_InterpreterEntryTrampoline + 268
23  ???                                 0x000000032048b6f0 0x0 + 13426538224
24  ???                                 0x000000032048c9e4 0x0 + 13426543076
25  ???                                 0x000000032048d328 0x0 + 13426545448
26  ???                                 0x0000000320433be8 0x0 + 13426179048
27  ???                                 0x00000003201fddf8 0x0 + 13423861240
28  ???                                 0x00000003201fe22c 0x0 + 13423862316
29  ???                                 0x00000003201fe474 0x0 + 13423862900
30  ???                                 0x00000003204e32c0 0x0 + 13426897600
31  ???                                 0x00000003204e7dec 0x0 + 13426916844

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.

Activity

  1. isker commented on Mar 22, 2026

    @isker
    Contributor

    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.

  2. added
    v8 engineIssues and PRs related to the V8 dependency.
    v24.xIssues that can be reproduced on v24.x or PRs targeting the v24.x-staging branch.
    on Mar 27, 2026
  3. changed the title [-]Segfault[/-] [+]Crash in V8 GC on v24.x and earlier[/+] on Mar 27, 2026
  4. ChrisRogers-TheNBS commented on Apr 21, 2026

    @ChrisRogers-TheNBS

    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-sparkplug flag i.e.

    node --no-sparkplug ./node_modules/.bin/jest

    instead of the usual npx jest or npm run test if you have jest configured in there.

  5. joshkel commented on Jun 5, 2026

    @joshkel
    Contributor

    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*) + 248
    
  6. MichalKantor commented on Jul 15, 2026

    @MichalKantor

    Another 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 SIGSEGV at address 0xe.

    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-*.ips reports:

    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-sparkplug 13.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 ChildProcessWorker forks child processes. Jest does use vm to execute test files. So across these reports the common factor looks like vm + 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-start does not help (3/40). I tried it because of the LeftTrimmed symbol name, and verified via process.execArgv inside 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-sparkplug nor --no-move-object-start is permitted in NODE_OPTIONS, and .bin/jest is a shell shim that cannot take node flags, so the invocation has to be node --no-sparkplug node_modules/jest/bin/jest.js. The workers inherit it because jest-worker forks with execArgv.

  7. eezy0 commented on Aug 20, 2026

    @eezy0

    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-sparkplug 0 / 6
    26.7.0 + --no-sparkplug 0 / 4

    So the 24 → 26 move did not help here, and --no-sparkplug remains 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, and node_modules/.bin/jest is a shell shim that cannot take node flags, so the entry point has to be invoked directly. Workers inherit it through execArgv:

    {
      "scripts": {
        "test": "node --no-sparkplug node_modules/jest/bin/jest.js"
      }
    }
  8. 2 remaining items

  9. nikzanda commented on Sep 21, 2026

    @nikzanda

    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/parser 8.62.0, TypeScript 6.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, with KERN_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_BaselineOutOfLinePrologue
    

    Reduced reproduction

    I reduced the original application test suite to 64 identical Jest test files, each importing @typescript-eslint/parser and 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-gc are 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.cjs in 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_ROOT is the directory containing the dependency installation's package.json and node_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-sparkplug All 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-sparkplug All 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 vm contexts 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?

  10. evoradance commented on Sep 23, 2026

    @evoradance
    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.
    
  11. added a commit that references this issue on Sep 29, 2026
  12. tianjos commented on Oct 2, 2026

    @tianjos

    Still reproducing on v24.21.0 (the current Latest LTS), which is after #65753 was merged into v24.x-staging but 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-sparkplug removes it completely, which lines up with the BaselineOutOfLinePrologue diagnosis.

    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. No worker_threads and no direct vm use in the test code, though jest's own runtime compiles modules through vm. 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 --runInBand crashes, 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-sparkplug 16/16 clean
    24.21.0 13.6.233.17-node.53 --runInBand 10/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=SIGSEGV and 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-sparkplug has to be passed to the binary directly — NODE_OPTIONS=--no-sparkplug is 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_ADDRESS at 0x6 and 0xe across 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_InterpreterEntryTrampoline
    

    Happy to run anything against this workload if a 24.x build with the backport would be useful to validate before release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    v24.xIssues that can be reproduced on v24.x or PRs targeting the v24.x-staging branch.v8 engineIssues and PRs related to the V8 dependency.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions