[BUG] Native installer (2.1.243) segfaults in newlocale/free during startup on glibc 2.44 (CachyOS)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
The native binary (Bun-based) crashes with SIGSEGV on every launch attempt, including during
the installer's own post-install step ("$binary_path" install). This reproduces 100% of the
time and matches the known Bun-runtime-on-new-glibc crash family (see #76241, #84293, #69980,
#62747), but glibc 2.44 is newer than any glibc version mentioned in those threads, so filing
this separately in case it needs a distinct fix/allowlist entry.
Environment
- Claude Code version: 2.1.243
- Install method: native installer (
curl -fsSL https://claude.ai/install.sh | bash) - OS: CachyOS (Arch-based, rolling release)
- Kernel: 7.2.0-1-cachyos
- glibc version: 2.44
- CPU: x86_64, AVX/AVX2/AVX-VNNI all present (confirmed via
/proc/cpuinfo, ruled out the
no-AVX crash class)
- Locale: LANG=en_US.UTF-8, LC_ALL=en_US.UTF-8 (confirmed valid/installed via
locale -a,
ruled out broken-locale-env class)
- Also have
claude-desktopinstalled from AUR (packageclaude-desktop-1.34493.1-1,
provides /usr/bin/claude-desktop and /usr/lib/claude-desktop). This is a separate
Electron-based app and does not appear related to the native CLI binary's install path
(~/.local/share/claude), but flagging it in case of any shared state, cache, or
binary-name collision worth ruling out.
The crash is inside free(), called from glibc's __newlocale, suggesting a bad free/double
free during locale initialization inside the bundled Bun runtime — possibly related to glibc's
own recent locale-path changes (glibc 2.44 changelog includes "locale: newlocale memory leak in
LOCPATH parsing and on error paths").
What I've Already Ruled Out
- Not a missing-AVX issue: CPU reports
avx avx2 avx_vnniin/proc/cpuinfo. - Not a broken/missing locale:
LANG/LC_ALLpoint toen_US.UTF-8, which is present in
locale -a output.
- Not a musl/glibc binary mismatch: confirmed system is glibc via
ldd --version
(ldd (GNU libc) 2.44), and ldd /bin/ls resolves cleanly.
Related Issues
- #76241
- #84293
- #69980
- #62747
What Should Happen?
The binary should start (or at minimum print a diagnosable error) instead of segfaulting
during runtime/locale initialization.
Error Messages/Logs
Stack trace of thread 3186:
#0 0x0000000001d10458 free (2.1.243 + 0x1b10458)
#1 0x00007f589663a1ab __newlocale (libc.so.6 + 0x3a1ab)
#2 0x0000000001abd282 n/a (2.1.243 + 0x18bd282)
#3 0x00007f58966b2584 n/a (libc.so.6 + 0xb2584)
#4 0x00007f58966b26a9 pthread_once (libc.so.6 + 0xb26a9)
#5 0x0000000001abc2d2 n/a (2.1.243 + 0x18bc2d2)
#6 0x0000000001ace6cf n/a (2.1.243 + 0x18ce6cf)
#7 0x0000000001abdc0a n/a (2.1.243 + 0x18bdc0a)
#8 0x0000000001abe9c4 n/a (2.1.243 + 0x18be9c4)
#9 0x00007f58966b2584 n/a (libc.so.6 + 0xb2584)
#10 0x00007f58966b26a9 pthread_once (libc.so.6 + 0xb26a9)
#11 0x0000000001abea15 n/a (2.1.243 + 0x18bea15)
#12 0x0000000001abead7 n/a (2.1.243 + 0x18bead7)
#13 0x0000000001abb922 n/a (2.1.243 + 0x18bb922)
#14 0x0000000001abb724 n/a (2.1.243 + 0x18bb724)
#15 0x0000000001aadadd n/a (2.1.243 + 0x18adadd)
#16 0x00007f5896627e23 __libc_start_main (libc.so.6 + 0x27e23)
#17 0x0000000001953a2e n/a (2.1.243 + 0x1753a2e)
ELF object binary architecture: AMD x86-64
Steps to Reproduce
curl -fsSL https://claude.ai/install.sh | bash- Installer downloads the binary, then fails during its own install step:
Setting up Claude Code...
bash: line 226: 3740 Segmentation fault (core dumped) "binary_path" install ${TARGET:+" TARGET"}
Installation was killed before it could finish (exit code 139).
- Running the resulting binary directly at
~/.local/share/claude/versions/2.1.243also
segfaults immediately, with no arguments needed.
claude uninstallon a previously-installed copy of this same version also segfaults
(same crash signature), rather than failing gracefully.
Claude Model
None
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.205
Claude Code Version
2.1.243
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
Other
Additional Information
_No response_
8 Comments
i encountered same issue for my cachyos, have to uninstall and reinstall 2.1.241 and disable update
Can confirm on a second CachyOS system (kernel 7.2.0-1-cachyos, glibc 2.44, x86_64 with AVX2). Identical stack trace — free resolving into the binary's own allocator, called from glibc's __newlocale under pthread_once, SEGV_MAPERR.
Additional data points that narrow this down:
The regression window is much tighter than 2.1.205. I downloaded builds directly from https://downloads.claude.ai/claude-code-releases/<version>/linux-x64/claude and tested on the same host, same glibc 2.44:
2.1.241 linux-x64: works (--version prints fine)
2.1.243 linux-x64: segfaults (haven't tested 2.1.242)
So whatever broke landed in 2.1.242 or 2.1.243 — likely a Bun runtime bump between those releases, not glibc 2.44 itself, since 2.1.241 runs on the exact same libc.
The musl build is unaffected and is a viable workaround:
curl -fsSL https://downloads.claude.ai/claude-code-releases/2.1.243/linux-x64-musl/claude -o claude
2.1.243 linux-x64-musl runs fine on the same host (needs the musl package installed and its dynamic linker present at /lib/ld-musl-x86_64.so.1 — on Arch: pacman -S musl && ln -sf /usr/lib/musl/lib/libc.so /usr/lib/ld-musl-x86_64.so.1). This is consistent with the crash being a glibc-symbol-interposition problem: glibc's locale init calls free(), which resolves to the allocator exported by the executable instead of glibc's own.
Also ruled out: crashes identically with network disabled (unshare -n), ASLR disabled (setarch -R), and LC_ALL=C LANG=C. And the same 2.1.243 linux-x64 binary runs fine in an Ubuntu 24.04 container (glibc 2.39) on the same kernel.
Same crash on a different distro, and I think I have the root cause: 2.1.243 exports
malloc/free/calloc/reallocas global dynamic symbols, which interposes over glibc's own allocator. 2.1.241 does not export them.Confirmation on a second system
~/.local/share/claude/versions/claude-desktopAUR package installed <!-- ← vérifie et corrige si besoin -->Identical signature to the original report:
Note that the faulting address
0x1d10458is identical to the one in the original report, on adifferent machine and a different distro. That only happens with a non-PIE executable loaded at a
fixed address — which is exactly the condition that makes the bug below fatal.
Regression window is much narrower than reported
The original report lists 2.1.205 as the last working version. On this machine, with four versions
still on disk:
So the regression is between 2.1.241 and 2.1.243, not somewhere after 2.1.205.
Root cause: the binary exports the allocator symbols
2.1.241 exports nothing. 2.1.243 exports all four as global (
T) symbols, versionedGLIBC_2.2.5.The addresses line up exactly with the crash:
nm:freeat0x1d10430rip 0x1d10458 <free+40>→ 40 bytes into that samefree0x1953000–0x5486000), not inlibc.so.6Since the executable's definitions win symbol resolution over every shared object, glibc's own
internal calls that go through the PLT get routed into the bundled allocator.
newlocale()allocateswith glibc's allocator and frees through the PLT, which lands in the bundled
free(), which receivesa pointer it never allocated — during pre-
maininitialization, before it is even fully set up.Backtrace (gdb, same as the coredump in the original report)
This is consistent with the glibc 2.44 changelog entry quoted in the original report
("newlocale memory leak in LOCPATH parsing and on error paths"): the reworked code path appears to
free through the PLT where it previously did not, which is what makes the pre-existing symbol export
newly fatal. The export is the bug; the glibc change only exposed it.
Additional data points ruling things out
straceshows the process dies with no syscall of its own afterset_robust_list— noopenat, no socket, no large JITmmap. It never reachesmain;--versionprints nothing.env -i HOME=$(mktemp -d) .../2.1.243 --version→ still SIGSEGV. Independent of environment,config,
~/.claude, and session state.lddoutput is byte-for-byte identical between 2.1.241 and 2.1.243 (same 7 objects). No newdependency was introduced.
stracesyscall sequences for 2.1.241 and 2.1.243 are identical up to the crash point.avx/avx2present in/proc/cpuinfo, and 2.1.241 runs fine on this same CPU.install.shfaults at the same addressas the already-installed one.
Suggested fix
Stop exporting the allocator symbols from the standalone executable — a linker version script
marking
malloc/free/calloc/realloc(and friends:posix_memalign,aligned_alloc,malloc_usable_size,strdup, …) aslocal, or building the bundled allocator without itslibc-override layer. Making the binary PIE would not help: executable definitions take precedence
in symbol resolution regardless.
Workaround for affected users
Point the launcher at the last good version and pin it:
plus
"autoUpdaterStatus": "disabled"in~/.claude/settings.json, otherwise the auto-updaterreinstalls the broken build. There is no user-side fix for the crash itself —
LD_PRELOADcannotoverride an executable's own symbol definitions.
Third CachyOS confirmation (kernel
7.2.0-1-cachyos, glibc 2.44, x86-64), same faulting address and same stack — nothing to add on root cause, @Obarun's allocator-interposition explanation and #89369's analysis match what I see byte for byte.What I do want to add is the second half of this bug: there is currently no supported way to pin yourself off 2.1.243, which is why @jgyy's "disable update" step is harder than it sounds. Concretely, on a native install:
1.
autoUpdates: falsein~/.claude.jsondoes not stop the updater. I had it set tofalseand the updater still ran and flipped the symlink:There is also a
"autoUpdatesProtectedForNative": truekey sitting next to it in the same file. I have not verified causation — but the observable behavior is that the documented-looking off switch is a no-op for native installs, so users who pin by symlink get silently re-broken on the next check. That is the "no upgrade path" in #89368 turned into a "no downgrade path".2. What actually works.
DISABLE_AUTOUPDATER=1is honored (the string is present in both the 2.1.241 and 2.1.243 binaries). Suppression has to be set before re-pointing the symlink, otherwise you just repeat the flip:Note on placement: a shell rc file only covers shells. If you launch Claude Code from a systemd service or any long-running supervisor, that process was started with the old environment and its children will still auto-update.
~/.config/environment.d/covers the systemd user session (after re-login); system-level units need the variable in the unit or itsEnvironmentFile.3. Verifying the pin. Don't verify from inside an existing Claude Code session — it inherits
CLAUDE_CODE_EXECPATHpointing at the old binary, so a broken workaround looks like it passed. From a fresh shell, run a real session (not just--version, which may short-circuit the periodic update check), then confirm both of these are unchanged:That held for me across a full headless run.
Also worth noting for anyone reading this before they get bitten: I received 2.1.243 because my
autoUpdatesChannelwas"latest". At the same momentstablewas 2.1.231, which is unaffected. If you are not deliberately dogfooding,stablewould have kept you out of this entirely.Fourth independent reproduction (Arch Linux, kernel
7.1.9-arch1-2, glibc2.44+r24+g16be1518495f, x86-64), same faulting address0x1d10458, same stack. Nothing to add on root cause: @Obarun's allocator-interposition explanation and the analysis in #89334 match what I see exactly.What I can add is a mechanism for the second half of this bug that I have not seen stated yet. @dispather's comment above covers why you cannot pin off 2.1.243 via the auto-updater. There is a separate and more basic problem:
install.sh's documented version argument cannot install an older version either, because the script ignores it when choosing the binary it executes.install.sh <version>cannot work while the current release is brokenThe script takes a version argument and validates it:
But
TARGETis not used to select the binary that gets downloaded and run. That is alwayslatest:TARGETis only ever passed through to the downloaded binary's owninstallsubcommand, on the last line before cleanup:So the pinning path is bootstrapped by a
latestbinary. Whilelatestis 2.1.243, that bootstrap binary hits this exact segfault before it can parse its own argument, and the requested version is never reached:The practical consequence: the recovery instruction being given to affected users in these threads is self-defeating on a clean machine. Anyone on a new-glibc distro without an older version already cached in
~/.local/share/claude/versions/has no supported route back to a working install:install.shis the documented path and it is broken by the same defect it would be used to escape. Downloading a known-good binary directly fromdownloads.claude.ai(as @zytoc did above) works, but that is not a documented interface.This is worth separating from the crash itself because it survives the crash fix: any future startup regression in
latestre-breaks the version-pinning escape hatch the same way. Selecting the bootstrap binary byTARGETwhen one is supplied, rather than always bylatest, would make the flag do what its usage string promises.Local reproduction detail
claude --versionis enough; no config or network access needed. 2.1.241 on the same machine is fine.<details>
<summary>Core dump (<code>coredumpctl info</code>)</summary>
</details>
Reached here by auto-update from 2.1.241 to 2.1.243; 2.1.242 was never cached locally, so I cannot bisect that boundary myself, but #89334 establishes the allocator export landing in 2.1.242.
claude doctoris not usable for diagnosis, since the affected binary segfaults before it runs.My only recovery was manually repointing the
~/.local/bin/claudesymlink at the cached 2.1.241.Fifth reproduction, Arch Linux, same artifact and same fault address. Two data points below that I have not seen in this thread or in #89334 / #89369 yet.
Environment
Same binaries as everyone else
The 2.1.242 hash matches @kay-ws's copy in #89334 and the 2.1.243 hash matches #89369, so this is the shipped artifact, not a bad download.
coredumpctlstack is identical to the one in the report (freeat0x1d10458in the executable, called from__newlocale, underpthread_once,SEGV_MAPERR). Still crashes withLC_ALL=C LANG=C.1. The embedded Bun build did change between 2.1.241 and 2.1.242
#89369 says both binaries embed "Bun v1.4.0" and concludes it is not a runtime bump. The version string is the same, but the build hash behind it is not:
So 2.1.242 moved to a newer Bun 1.4.0 commit (
19bc277a0) and 2.1.243 kept it. That lines up with the.dynsymdiff already posted:fdb5e06ccdoes not export the allocator,19bc277a0does. The Claude Code side changed too (GIT_SHAc87e274->2482ce9->8565f92) but the crash is pre-main in the runtime, so the Bun commit is the relevant one.For completeness, 2.1.243 also exports the glibc-internal aliases, not just the public names:
2. Every install path re-breaks a pinned machine
Adding to @dispather's and @steevee's points. I pinned the symlink to 2.1.241 and then tried the two documented ways of getting a specific version; both flipped me back onto the crashing build without checking that it can start:
~/.claude/.last-update-result.jsonrecords each of these as"outcome":"success". The updater also flipped the symlink on its own about ten minutes after my first manual repin. What held it in place wasDISABLE_AUTOUPDATER=1; setting it in theenvblock of~/.claude/settings.jsonworks and survives shell changes:(
"autoUpdates": falseat the top level is rejected by the settings schema on 2.1.241:Unrecognized field: autoUpdates.)Suggestion for the fix release: have
install/upgraderun<new binary> --versionbefore moving the symlink, and leave it alone if that exits non-zero. It would have caught this on the first machine.One correction to the workaround that is now standing in this thread, from a re-break I hit 30 minutes after pinning.
@dsolis-deloryx's advice above —
"env": { "DISABLE_AUTOUPDATER": "1" }in~/.claude/settings.json— is correct, and the "survives shell changes" part is true. But that file is per config directory, and the symlink it protects is not. If you run more than one Claude Code profile on the same machine (CLAUDE_CONFIG_DIR, i.e.~/.claude2and friends), a session started under a profile that lacks the key will run the updater and flip~/.local/bin/claudefor every profile, including the one you just pinned.What I observed
I had already done the full pin:
DISABLE_AUTOUPDATER=1in~/.claude/settings.jsonenv, plus a shell rc and~/.config/environment.d/, and repointed the symlink at 2.1.241. 10 minutes later I started a session under a second config directory and the CLI was dead again.The symlink mtime and the second profile's result file agree to the nanosecond (
…18.489296011/…18.489Z), while the primary profile's file was never rewritten. So the writer was the second profile's updater, and it moved a symlink that the primary profile depends on.The reason is just that the suppression source is scoped and the target is not. At that point the two files read:
Adding the key to the second file stopped it; the pin has held since.
Why the other layers did not catch it
Worth stating because it makes the failure look like the workaround "not working" when it is actually placement:
~/.config/environment.d/does not apply until the next login of the systemd user session. I had created it 10 minutes earlier, so it was inert (systemctl --user show-environmentdid not list it).Both of those are the same class as the systemd note I posted earlier, but the config-dir one is different in kind: the variable can be correctly set and persistent for one profile and simply not exist for another, on the same machine, at the same moment.
Suggestion
Two small things, either of which would have prevented this:
<new binary> --versionbefore moving the symlink and leave it alone on non-zero exit — also covers this case, and covers it for every profile at once. That seems like the higher-value fix of the two.For anyone pinning right now on a multi-profile machine: set the key in every
<config-dir>/settings.json, not just~/.claude. Checking is one line:(Environment as before: CachyOS, kernel
7.2.0-1-cachyos, glibc 2.44, x86-64, native install. Nothing new on the crash itself — @Obarun's and #89334's allocator-interposition analysis still matches what I see.)Confirming on CachyOS (kernel 7.2.0-1-cachyos, glibc 2.44, x86_64) with the same signature —
freeat2.1.243 + 0x1b10458,rdi = 0x0,segfault at 0.Rather than repeat what's established, one data point that may save Arch-family users a detour: the trigger is in glibc 2.44 itself, not the recent 2.44 snapshot bump.
Arch/CachyOS shipped
2.44+r5+g7cba77790f32-1 → 2.44+r24+g16be1518495f-1on 2026-08-18, landing close enough to this regression that it's an easy thing to suspect. I downgraded glibc to r5, retested, and restored. The coredumps record both:| Cores | glibc |
pthread_once|freein binary ||---|---|---|---|
| 8 | 2.44+r24 |
libc.so.6 + 0xb2df9|2.1.243 + 0x1b10458|| 2 | 2.44+r5 |
libc.so.6 + 0xb2c79|2.1.243 + 0x1b10458|Differing libc offsets confirm the two builds actually loaded; the binary offsets are byte-identical across all ten. Same crash on both snapshots.
So for anyone on Arch derivatives who updated glibc around 2026-08-18: that bump is not it, and downgrading it won't help. Consistent with @zytoc's point that 2.1.241 runs on the same libc.
Register-level confirmation of the
free(NULL)mechanism, since it's cheap to include:free(NULL)is a documented no-op in glibc, so__newlocalecalling it unconditionally is correct — the interposedfreejust doesn't guard against NULL.