[BUG] Recurring mid-session SIGSEGV of native linux-x64 binary across 2.1.210-2.1.278 (Bun 1.4.3, glibc 2.42) - distinct from the glibc 2.44 startup cluster

Status Open
Reported on v2.1.278
Maintainer reply None cached
Activity 3 comments · opened Sep 20, 2026

Summary

The native linux-x64 binary SIGSEGVs mid-session, not at startup, and has done so repeatedly across nine releases over the last two months. This appears distinct from the large cluster of startup-segfault reports (#89370, #89412, #89415, #89504, #89732, #89759, #92749, #94462), which are tied to glibc 2.44 / Arch and fault inside __newlocale during init. This machine runs glibc 2.42, startup is reliable, and the crash lands after a long, healthy session.

Environment

| | |
|---|---|
| Claude Code | 2.1.278 (native install, ~/.local/share/claude/versions/2.1.278) |
| Bundled Bun | 1.4.3 |
| OS | Mageia 10 |
| Kernel | 6.18.44-desktop-1.mga10 x86_64 |
| glibc | 2.42-10.mga10 |
| Terminal | Konsole |

Crash history

systemd-coredump has recorded six SIGSEGVs of the claude binary on this machine:

| Date | Version |
|---|---|
| 2026-07-28 | 2.1.210 |
| 2026-07-28 | 2.1.218 |
| 2026-08-22 | 2.1.233 |
| 2026-09-10 | 2.1.266 |
| 2026-09-11 | 2.1.268 |
| 2026-09-20 | 2.1.278 |

So this is not a regression introduced by one release — it spans 2.1.210 through 2.1.278 and at least three different bundled Bun versions.

Evidence that it is not a startup crash

For the 2026-09-20 crash (PID 365693):

  • The session transcript's first entry is 2026-09-20T08:28:52Z; its last entry is 2026-09-20T09:54:48Z. The core was dumped at 09:56:21Z — roughly 1 h 27 min into the session, and ~93 s after the last recorded activity.
  • The thread list in the core shows two distinct generations of thread IDs: the startup threads (365697–365811, contemporaneous with the main thread's PID 365693) and a much later generation (509729, 509731, 509733, 509755, 510082) created once the system PID counter had advanced by ~144 000. Those later threads are all parked in pthread_cond_timedwait.
  • The transcript's final line is structurally complete, so the process was not killed mid-write; it faulted while idle or between operations.

Faulting thread

Binary is stripped, so only offsets are available. The shape is a fault handler re-raising: __restore_rt at #6 means frames #7 and above are the original fault, and frames #12–#14 are the same address repeated, which suggests a recursive path.

Stack trace of thread 365693:
#0  0x00007f5f78068c6c __pthread_kill_implementation (libc.so.6 + 0x92c6c)
#1  0x00007f5f78011da6 raise (libc.so.6 + 0x3bda6)
#2  0x00000000037c88b2 n/a (claude 2.1.278 + 0x35c88b2)
#3  0x00000000037c7c9d n/a (claude 2.1.278 + 0x35c7c9d)
#4  0x00000000037c8664 n/a (claude 2.1.278 + 0x35c8664)
#5  0x0000000003b2ecdc n/a (claude 2.1.278 + 0x392ecdc)
#6  0x00007f5f78011ef0 __restore_rt (libc.so.6 + 0x3bef0)     <-- signal boundary
#7  0x0000000004beb15d n/a (claude 2.1.278 + 0x49eb15d)       <-- fault site
#8  0x0000000003b8cfe6 n/a (claude 2.1.278 + 0x398cfe6)
#9  0x0000000004ebcf5f n/a (claude 2.1.278 + 0x4cbcf5f)
#10 0x00000000046e5acb n/a (claude 2.1.278 + 0x44e5acb)
#11 0x00007f5f73c8c017 n/a (n/a + 0x0)                        <-- JIT frame
#12 0x0000000003cbfc55 n/a (claude 2.1.278 + 0x3abfc55)
#13 0x0000000003cbfc55 n/a (claude 2.1.278 + 0x3abfc55)
#14 0x0000000003cbfc55 n/a (claude 2.1.278 + 0x3abfc55)
#15 0x0000000003c9fcc4 n/a (claude 2.1.278 + 0x3a9fcc4)
#16 0x0000000004b693c9 n/a (claude 2.1.278 + 0x49693c9)
#17 0x0000000004b64908 n/a (claude 2.1.278 + 0x4964908)
#18 0x0000000004e2865e n/a (claude 2.1.278 + 0x4c2865e)
#19 0x0000000004f83494 n/a (claude 2.1.278 + 0x4d83494)
#20 0x000000000233c79d n/a (claude 2.1.278 + 0x213c79d)
#21 0x00007f5f740d6f1a n/a (n/a + 0x0)                        <-- JIT frame
#22 0x00007f5f740d6f41 n/a (n/a + 0x0)                        <-- JIT frame
#23 0x0000000003c9fcc4 n/a (claude 2.1.278 + 0x3a9fcc4)
#24 0x00000000046287f7 n/a (claude 2.1.278 + 0x44287f7)
#25 0x000000000497f433 n/a (claude 2.1.278 + 0x477f433)
#26 0x000000000226af80 n/a (claude 2.1.278 + 0x206af80)
#27 0x00000000022b7d26 n/a (claude 2.1.278 + 0x20b7d26)
#28 0x0000000002fd5933 n/a (claude 2.1.278 + 0x2dd5933)
#29 0x000000000213df88 n/a (claude 2.1.278 + 0x1f3df88)
#30 0x0000000002fd5bd7 n/a (claude 2.1.278 + 0x2dd5bd7)
#31 0x0000000001d84226 n/a (claude 2.1.278 + 0x1b84226)
#32 0x0000000002047b06 n/a (claude 2.1.278 + 0x1e47b06)
#33 0x000000000204289b n/a (claude 2.1.278 + 0x1e4289b)
#34 0x000000000214258c n/a (claude 2.1.278 + 0x1f4258c)
#35 0x00007f5f77ffc2f9 __libc_start_call_main (libc.so.6 + 0x262f9)
#36 0x00007f5f77ffc3ab __libc_start_main@@GLIBC_2.34 (libc.so.6 + 0x263ab)
#37 0x0000000001922c2e n/a (claude 2.1.278 + 0x1722c2e)

Reproduction

No deterministic trigger identified. Ordinary interactive use; crashes recur every week or two.

What would help

  1. Shipping symbols, or a .debug sidecar / build ID, so offsets like +0x49eb15d can be resolved. As it stands, every Linux crash report is just offsets and no one can act on them. The binary does carry BuildID[sha1]=83effae1a76aa74ce70326f83068a1242f99094a, but there is nothing public to match it against.
  2. I still have the 98.5 MB core for the 2026-09-20 crash and am happy to run any requested gdb commands against it, or send it privately. Earlier cores have been rotated away, so it will not last indefinitely.

Note on the workaround question

There is currently no way to run Claude Code off the Bun runtime. The npm package @anthropic-ai/claude-code is a 184 KB wrapper whose optionalDependencies fetch the same 234 MB native binary (@anthropic-ai/claude-code-linux-x64); its engines: node >=22 only governs the postinstall script. So for users hitting Bun-runtime faults there is no Node-based fallback to switch to. A supported way to run under Node would be a genuine mitigation for this whole class of report.

View original on GitHub ↗

3 Comments

sgeard · 25 days ago

I've now examined the retained core under gdb. Two corrections to the original report, and a fault classification that I think rules this firmly out of the startup-segfault cluster.

Corrections

  • The binary does have a build ID: 83effae1a76aa74ce70326f83068a1242f99094a (.note.gnu.build-id). I wrongly said it did not; I've fixed that sentence in the issue body.
  • file reports the binary as "not stripped", but that is misleading: .symtab contains 1234 entries and all of them are undefined imports (U abort, U accept, …). There are no defined symbols for any Bun or Claude Code function, so neither gdb nor eu-stack can resolve a single frame. The request for symbols stands.

This is a #GP, not a null dereference

I recovered the kernel's saved sigcontext from the rt_sigframe on the crashed thread's stack (trampoline pointer found at 0x7ffc391e67f8):

| Field | Value |
|---|---|
| trapno | 0xd (13) — #GP, general protection fault |
| err | 0 |
| cr2 | 0 |
| RIP | 0x4beb15d (binary + 0x49eb15d) |
| RSP | 0x7ffc391e73e0 |
| r14 | 0x523c9baf8f90c376 |
| r8 | 0x24d47470cc8 |
| rax | 0x24d3ff00010 |
| rbx / r13 | 0x24d38180060 |
| r12 | 0x24d3f30c310 |
| r15 | 0x24d38220088 |

Trap 13 with cr2 = 0 means this is not a null-pointer dereference — it is a non-canonical address access. Every report in the startup cluster (#89390, #89732, #89759 et al.) is trap 14 with si_addr = NULL inside __newlocale. Different fault, different class of bug.

The faulting instruction and where the bad pointer came from

0x4beb139:  mov    (%r8),%r14          <-- r14 loaded from the heap here
0x4beb13c:  cmpq   $0x0,0x28(%r12)
0x4beb142:  jne    0x4beb747
0x4beb148:  mov    %r13,-0x58(%rbp)
0x4beb14c:  mov    0x8(%r12),%rax
0x4beb151:  mov    %rbx,%r13
0x4beb154:  test   %rax,%rax
0x4beb157:  je     0x4beb264
0x4beb15d:  mov    0x10(%r14),%ecx     <-- faults, r14 = 0x523c9baf8f90c376

r14 is loaded from (%r8) and dereferenced eight instructions later with no validation. 0x523c9baf8f90c376 is non-canonical (bits 63:48 are not a sign-extension of bit 47), hence #GP rather than a page fault.

The source memory is high-entropy data, not an object

Dumping around r8:

0x24d47470cb8:  0x1edd7c0bf9bba439  0x5c1843eb0ee49c0e
0x24d47470cc8:  0x523c9baf8f90c376  0x9560c66336047e1b   <-- (%r8)
0x24d47470cd8:  0xe566a3bebe19f6c3  0x217d5a03ba16cd55
0x24d47470ce8:  0xb1d3b07e74a4b498  0xd94649ab18203026

Every word in the neighbourhood is high-entropy. There are no object headers, no small integers, no canonical pointers — nothing that looks like a JS cell or a NaN-boxed JSValue.

Mapping the addresses against the core's LOAD segments, r8 and all the live object registers (rax, rbx, r12, r13, r15) fall inside a single 1024 MiB RW reservation at 0x24d37000000–0x24d77000000 — i.e. the JSC cage. RIP is in the binary's 58.9 MiB text segment (0x1922000–0x5408000), so the faulting frame is native code, not JIT; frames #11, #21 and #22 of the backtrace are at 0x7f5f73c8c017 / 0x7f5f740d6f1a / 0x7f5f740d6f41, outside any file-backed mapping, and are the JIT frames.

So: native code read a 64-bit word out of the cage and used it directly as an object pointer, but that word came from a region holding buffer-like data rather than object graph. That is the signature of a stale or confused pointer — a use-after-free or a type confusion — rather than an allocator/libc interaction. The cage is what turned it into a clean #GP instead of silent corruption.

Also worth noting

Frames #12–#14 are the same address 0x3cbfc55 three times. That is not runaway recursion — the call site is a generic indirect dispatch:

0x3cbfc46:  mov    0x18(%r10),%rdx
0x3cbfc4a:  mov    %rdx,(%rsp)
0x3cbfc4e:  mov    0x20(%r10),%r10
0x3cbfc52:  call   *%r10

so three levels of nested dispatch through a function-pointer table, which is unremarkable.

Offer

I still have the core (98.5 MiB compressed, 5.3 GiB expanded) and the exact binary, kept together. With even a stripped-symbol sidecar for build 83effae1a76aa74ce70326f83068a1242f99094a I could name the function at +0x49eb15d and the allocation site. Happy to run any gdb commands you want against it, or to hand the core over privately.

sgeard · 24 days ago

Second crash 39 hours later — and the core says this is a wild write, not a bad null check

Same machine, same binary image (2.1.278, sha256 5c4735937844e84f8a93306e841a5b0e12252909b07870f789b190468da147ab, BuildID[sha1]=83effae1a76aa74ce70326f83068a1242f99094a), same boot: another SIGSEGV at 2026-09-21 01:03:33 BST, PID 509935. Both cores are preserved locally and I am happy to hand either one over, or run further commands against them, if that is useful.

The two crashes look different at the top of the stack but are the same underlying fault, and the second core is much more informative because the fault landed somewhere I could reason about.

Fault

Registers recovered from the rt_sigframe on the main stack (gdb cannot unwind past the signal frame here; locate __restore_rt = libc+0x3bef0 on the stack, then read uc_mcontext at +40):

rip 0x0000000004576048   trapno 13 (#GP)   err 0   cr2 0
rax 0x82ed85d935c35000   rcx 0x82ed85d935c34000   (= rax & ~0x3fff)
rdi 0x00000356700c6800   rbx 0x00000356700c6800
r14 0x0000035665180000 (VM)   r15 0x00000356700c4000

trapno 13 with err 0 and cr2 0 means a non-canonical address, i.e. a #GP — not a null dereference. The 2026-09-20 crash was the same: trapno 13, err 0, cr2 0, with r14 = 0x523c9baf8f90c376. This remains clearly distinct from the glibc-2.44 startup cluster, which is trap 14 with si_addr = NULL.

The faulting code is JSC's isMarked()

0x4576017:  mov  0x70(%rdi),%rax          ; cell/CellContainer word out of the object
0x457601b:  test $0x1,%al                 ; PreciseAllocation vs MarkedBlock encoding
0x457602d:  and  $0xfffffffffffffff8,%rax
0x457603a:  test $0x8,%al
0x457603e:  mov  %rax,%rcx
0x4576041:  and  $0xffffffffffffc000,%rcx ; MarkedBlock::blockFor() — 16 KiB alignment
0x4576048:  mov  0x20(%rcx),%edx          ; <-- FAULTS: block's marking version
0x457604b:  mov  0x228(%r14),%esi         ; heap's current marking version
0x4576054:  cmp  %esi,%edx
0x4576063:  mov  0x28(%rcx,%rax,1),%rax   ; mark bitmap
0x4576068:  bt   %rdx,%rax

So the collector formed a MarkedBlock* from a garbage cell word and died reading the block's marking version.

The cell is destroyed, header and all

[rdi+0x70] = 0x82ed85d935c35005 — garbage with bit 0 set, so it is taken as a cell. But the whole 128-byte object at rdi is garbage, including word 0 where the StructureID + type/flags belong:

0x356700c6800:  0x993cfc2e1485ce90  0x144e4c9ebe604d1b  0xb04c7ea888246e10  0xa42e181906586808
0x356700c6810:  0x92204443ce8828b2  0x47428b365a454467  0xe0246308f5b0c9ba  0xbeb7d947327f65a7
...
0x356700c6870:  0x82ed85d935c35005  <-- the word the GC followed

The containing block is valid: 0x356700c4000 is 16 KiB aligned, its marking version is 146 and matches the heap's, and its cells are a uniform 128-byte class whose header word is 0x00000c0000a906b0 in slot after slot with mostly-zero bodies. Entropy map of that block at 64-byte granularity:

+0x0000..+0x2800  structured
+0x2800..+0x2900  GARBAGE   256 bytes   <-- the cell the GC died on
+0x2900..+0x2c00  structured
+0x2c00..+0x2d00  GARBAGE   256 bytes
+0x2d00..+0x4000  structured

Four live cells overwritten with high-entropy data while their immediate neighbours are untouched.

The same fingerprint in the 2026-09-20 core

I scanned both 1 GiB JSC cage reservations for isolated high-entropy 256-byte units — the cage legitimately holds plenty of contiguous high-entropy buffer data, so the signature is an isolated run sitting inside a cell block:

| Core | Matching blocks | Hot offsets | Faulting load |
|---|---|---|---|
| 2026-09-21 | 0x356700c4000 | +0x2800, +0x2c00 | 0x356700c6800 = +0x2800 |
| 2026-09-20 | 0x24d47470000 | +0x800, +0xc00 | 0x24d47470cc8 = +0xcc8 |
| 2026-09-20 | 0x24d4582c000 | +0x3500, +0x3900 | — |

Three sites across two independent crashes. Every one is two 256-byte runs exactly 0x400 apart, 256-byte aligned. In both crashes the faulting load came out of one of them.

The 32 bytes at the 2026-09-21 site occur exactly once in the 5.3 GiB core, so this is not a stale copy of a buffer that is still live somewhere — whatever produced the data has since gone away.

Reading

This is an out-of-bounds or use-after-free write, not a GC logic slip: something writes 256-byte payloads at a 1024-byte stride into memory that JSC has already handed back out as cells, and the next collection to visit those cells dies. High-entropy 256-byte payloads in a process that is otherwise idle suggest a freed network or crypto buffer still being written after its backing store was reclaimed.

That also explains why the faulting RIP differs between the two crashes (binary-relative 0x49eb15d vs 0x4376048): the GC is simply whoever trips over the damage first, so the visible crash site is incidental. Both faulted on the main thread, but by different routes — the 2026-09-20 stack has JIT frames below the fault, so the collection was entered from running JavaScript, whereas the 2026-09-21 stack has none and runs straight back to __libc_start_main. Chasing the faulting instruction is unlikely to lead anywhere — the writer is the bug.

Both crashes hit while the session was idle

| | 2026-09-20 | 2026-09-21 |
|---|---|---|
| Session span | 08:28:52Z → 09:54:48Z | 2026-09-20 09:55:48Z → 2026-09-20 23:00:24Z (~13 h) |
| Core dumped | 09:56:21Z, ~93 s after last activity | 2026-09-21 00:03:33Z, ~63 min after the last turn |
| Faulting thread | main thread, with JIT frames below the fault | main thread, no JIT frames — straight from __libc_start_main |

Platform note: this is a brand-new OS underneath an unchanged binary

Worth stating explicitly, because it may be the reason the crash rate jumped. This machine was upgraded Mageia 9 → Mageia 10 on 2026-09-19, two days before this crash:

| | before | after |
|---|---|---|
| Kernel | 6.6.141-1.mga9 | 6.18.44-1.mga10 (installed 19 Sep 14:01 BST) |
| glibc | 2.36-59.mga9 | 2.42-10.mga10 (installed 19 Sep 12:28 BST) |
| gcc | — | 15.2.0-1.mga10 |
| Desktop | Plasma 5 / X11 | Plasma 6 / Wayland |

That is six glibc releases (2.36 → 2.42) and twelve kernel releases (6.6 → 6.18) in one step, so malloc, mmap/VMA handling and the page-fault paths under the binary all changed at once.

Crash counts either side of that line, with the Claude Code binary unchanged:

  • Mageia 9, 2026-07-28 → 2026-09-18: 5 crashes in ~7.5 weeks (2.1.210, .218, .233, .266, .268) — about one every nine days.
  • Mageia 10, 2026-09-20 → 2026-09-21: 2 crashes in 39 hours, both 2.1.278.

So the new OS did not introduce the bug — it goes back to July across nine releases and at least three bundled Bun versions — but the rate is roughly an order of magnitude higher on it. A new kernel and a new allocator plausibly change the reuse timing that decides whether the errant write lands on a live cell. To be clear, this is glibc 2.42 on kernel 6.18, not the glibc 2.44 seen in the startup-crash reports.

Other tunables, for the record: THP enabled = always [madvise] never, vm.max_map_count = 65530, vm.overcommit_memory = 0, kernel.randomize_va_space = 2.

What would help

Symbolisation is still impossible from outside. file reports the binary as "not stripped", but all 1234 .symtab entries are undefined imports, so no frame resolves. A debug sidecar matching BuildID[sha1] 83effae1a76aa74ce70326f83068a1242f99094a would let me name the writer rather than describe its footprint. Failing that, an ASAN or debug build of the same release that I could run for a few days would very likely catch the write at its source, given the rate on this machine.

sgeard · 13 days ago

Another one, now on 2.1.287: four more releases since the 2.1.283 crash on 2026-09-27, and the crash still happens.

Crash details

  • 2026-10-02 04:36 BST, SIGSEGV, native binary versions/2.1.287, BuildID[sha1]=ca2032b38650b44e05b2074617d524c7475c80f0
  • The session was idle, as before: the last transcript entry was ~9 h before the fault.
  • Platform: this box is still on the completely new OS stack from the Mageia 9 → 10 upgrade (2026-09-19): kernel 6.18.44, glibc 2.42, gcc 15.2. Startup is reliable; the crashes are always mid-session.

What's different this time: the 09-20 and 09-21 cores faulted on the main thread, inside JSC marking. This one faulted on a worker thread (start_thread at the root). That suggests the heap corruption described above (two high-entropy 256-byte splats 0x400 apart over live cells) is also being hit by a background/concurrent thread, not only by synchronous GC on main.

Faulting thread (binary-relative offsets; frames #2–#5 look like Bun's crash handler re-raising):

#0  __pthread_kill_implementation (libc.so.6 + 0x92c6c)
#1  raise (libc.so.6 + 0x3bda6)
#2  2.1.287 + 0x31a13f2
#3  2.1.287 + 0x31a07dd
#4  2.1.287 + 0x31a11a4
#5  2.1.287 + 0x39b757c
#6  __restore_rt (libc.so.6 + 0x3bef0)
#7  2.1.287 + 0x444e9a1   <-- fault
#8  2.1.287 + 0x444b033
#9  2.1.287 + 0x444c0ed
#10 2.1.287 + 0x4427ea0
#11 2.1.287 + 0x44312ff
#12 2.1.287 + 0x43e4b64
#13 2.1.287 + 0x3956002
#14 2.1.287 + 0x39b8919
#15 start_thread (libc.so.6 + 0x90f42)
#16 __clone3 (libc.so.6 + 0x10ed6c)

Rate: four crashes in 13 days since the OS upgrade (09-20, 09-21, 09-27, 10-02, on 2.1.278 ×2, 2.1.283 and 2.1.287). Before the upgrade it was roughly one every 1–2 weeks.

I've kept the full core (~5 GiB uncompressed) and the matching binary for 2.1.278, 2.1.283 and 2.1.287. I can't symbolise them because the binary's .symtab has only undefined imports. If someone at Anthropic can resolve the offsets above against the 2.1.287 build, or wants the core, I'm happy to provide whatever helps.