Native linux-x64 binary segfaults on Arch Linux (glibc 2.44) — free() called from libc newlocale() crashes with NULL table pointer
Description
The native (Bun-compiled) claude binary segfaults on Arch Linux (rolling release) due to an allocator‑interposition conflict with a newer glibc runtime. The crash is deterministic (same crash address every run) and reproducible with both the install.sh bootstrapper and the pre‑downloaded binary run directly.
Root cause: the binary statically exports its own malloc/free symbols, which (via normal ELF symbol resolution) end up intercepting free() calls made internally by the system libc.so.6 (e.g. from newlocale() during process/locale bootstrap). On this glibc version the pointer being freed was allocated by the system libc's own allocator, not the binary's bundled one, so the bundled free() dereferences an uninitialized/mismatched heap-metadata table pointer (RAX == 0) and crashes.
I confirmed the exact same binary works fine under an older glibc, and that switching to the musl build avoids the bug entirely (see "Workaround" below).
Environment
- OS: Arch Linux (rolling release), running under WSL2
- Kernel:
6.18.33.2-microsoft-standard-WSL2 - glibc:
2.44+r24+g16be1518495f-1(Arch's current rolling package) - Claude Code version:
2.1.243 - Install method:
curl -fsSL https://claude.ai/install.sh | bash - Platform key resolved by installer:
linux-x64
Steps to Reproduce
curl -fsSL https://claude.ai/install.sh | bash
or, running the downloaded binary directly:
DOWNLOAD_BASE_URL="https://downloads.claude.ai/claude-code-releases"
version=$(curl -fsSL "$DOWNLOAD_BASE_URL/latest")
curl -fsSL -o ./claude "$DOWNLOAD_BASE_URL/$version/linux-x64/claude"
chmod +x ./claude
./claude install
Actual Behavior
The installer/binary segfaults immediately:
Setting up Claude Code...
bash: line 226: 1151 Segmentation fault "$binary_path" install ${TARGET:+"$TARGET"}
Installation was killed before it could finish (exit code 139).
dmesg shows a deterministic null-pointer dereference at the same code offset on every run:
claude-2.1.243-[...]: segfault at 0 ip 0000000001d10458 sp ... error 4 in claude-2.1.243-linux-x64[1b0f458,1953000+3b33000] likely on CPU ...
claude-2.1.243-linux-x64: claude-2.1.243-: potentially unexpected fatal signal 11.
A gdb backtrace on the raw downloaded binary shows the crash is inside the binary's own statically-linked free(), called from the system glibc's newlocale():
Program received signal SIGSEGV, Segmentation fault.
0x0000000001d10458 in free ()
#0 0x0000000001d10458 in free ()
#1 0x00007ffff7c3530a in newlocale () from /usr/lib/libc.so.6
#2 0x0000000001abd282 in ?? ()
#3 0x00007ffff7c9d7fc in ?? () from /usr/lib/libc.so.6
#4 0x00007ffff7c9d879 in pthread_once () from /usr/lib/libc.so.6
...
#16 0x00007ffff7c27892 in __libc_start_main () from /usr/lib/libc.so.6
rax = 0x0 rcx = 0x0 rdi = 0x0
=> 0x1d10458 <free+40>: mov (%rax,%rcx,1),%rdi ; RAX is a NULL table pointer
0x1d1045c <free+44>: test %rdi,%rdi
0x1d1045f <free+47>: je 0x1d1047f <free+79>
RAX (loaded via a rip-relative global at the top of free) is NULL, i.e. the binary's own heap-metadata table was never initialized for this allocation — consistent with free() being invoked on a pointer that the system libc's own malloc allocated internally (inside newlocale/pthread_once locale bootstrap), not the binary's bundled allocator.
Expected Behavior
claude install completes successfully, as it does on other Linux distributions.
Root-Cause Confirmation
- Setting
vm.mmap_rnd_bits=28(ruling out an ASLR/ address-layout theory) had no effect — crash address (0x1d10458) and register state were byte-for-byte identical across runs, i.e. it's deterministic, not layout-dependent. - Running the exact same downloaded
linux-x64binary inside a Docker container:
ubuntu:22.04(glibc 2.35) → installs successfully, no crash.- Host Arch Linux (glibc 2.44) → segfaults every time.
LC_ALL=C LANG=Cenv vars do not avoid the crash (still segfaults, same address).
This strongly suggests a regression/incompatibility introduced somewhere between glibc 2.35 and 2.44 in how newlocale() (or its internal pthread_once-guarded locale-category init path) interacts with a statically-linked allocator that overrides the process-wide malloc/free symbols.
Workaround
Downloading and running the musl build instead of the glibc build completely avoids the issue, since musl doesn't participate in this dynamic symbol interposition the same way:
sudo pacman -S musl # official Arch `extra` repo, ~3.7MB, does not touch system glibc
version=$(curl -fsSL https://downloads.claude.ai/claude-code-releases/latest)
curl -fsSL -o /tmp/claude-musl "https://downloads.claude.ai/claude-code-releases/$version/linux-x64-musl/claude"
chmod +x /tmp/claude-musl
/tmp/claude-musl install
This installs and runs claude successfully on the same host.
Suggested Fix
- Investigate why the bundled allocator's
free()is reachable fromlibc.so.6-internal calls (e.g.newlocale) on newer glibc, and/or avoid globally exportingmalloc/freesymbols from the binary in a way that shadows the system libc's own internal allocations. - As a pragmatic short-term mitigation,
install.sh/ the updater could detect the glibc version at install time and prefer the-muslbuild when the detected glibc is newer than whatever version the binary was validated against, since the musl build is already published and unaffected.
Happy to provide the full binary, a core dump, or further gdb/objdump output if useful.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗