[BUG] v2.1.242 segfaults on every launch (even `--version`) — interposed mimalloc `free` has no NULL check, glibc `newlocale` calls `free(NULL)` pre-main; v2.1.241 unaffected
Summary
v2.1.242 (native install, Linux x64) segfaults on every invocation, including claude --version. The crash is pre-main, during startup. v2.1.241 and earlier are unaffected.
Root cause: 2.1.242 is the first build to export its bundled mimalloc as versioned glibc allocator symbols, which interposes the allocator process-wide. The interposed free does not handle a NULL argument, so the first free(NULL) anywhere in the process — glibc's own newlocale makes one during startup — dereferences a null pointer.
free(NULL) is required to be a no-op (C17 7.22.3.3p2), so this will fire on any libc path that frees a null pointer.
Reproduction
$ ~/.local/share/claude/versions/2.1.242 --version
[1] 88020 segmentation fault (core dumped)
$ echo $?
139
Every launch, 100% reproducible, no config or arguments required.
Adjacent versions on the same machine, same glibc, same shell:
2.1.239: exit=0 2.1.239 (Claude Code)
2.1.240: exit=0 2.1.240 (Claude Code)
2.1.241: exit=0 2.1.241 (Claude Code)
2.1.242: exit=139 (SIGSEGV)
Backtrace
Signal: 11 (SEGV) si_code: SEGV_MAPERR
#0 0x0000000001d10458 in free () <- in the claude binary, not libc
#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
#5 0x0000000001abc2d2 in ?? ()
...
#16 0x00007ffff7c27892 in __libc_start_main () from /usr/lib/libc.so.6
#17 0x0000000001953a2e in ?? ()
Root cause
1. 2.1.242 newly interposes the process allocator. readelf --dyn-syms on the two builds:
$ readelf --dyn-syms --wide 2.1.241 | grep -E '\b(free|malloc|calloc|realloc|aligned_alloc|posix_memalign)\b'
(no output)
$ readelf --dyn-syms --wide 2.1.242 | grep -E '\b(free|malloc|calloc|realloc|aligned_alloc|posix_memalign)\b'
560: 0000000001d10320 145 FUNC GLOBAL DEFAULT 17 calloc@@GLIBC_2.2.5
700: 0000000001d102d0 79 FUNC GLOBAL DEFAULT 17 malloc@@GLIBC_2.2.5
862: 0000000001d10b70 122 FUNC GLOBAL DEFAULT 17 aligned_alloc@@GLIBC_2.16
927: 0000000001d10ab0 181 FUNC GLOBAL DEFAULT 17 posix_memalign@@GLIBC_2.2.5
1048: 0000000001d10430 126 FUNC GLOBAL DEFAULT 17 free@@GLIBC_2.2.5
1213: 0000000001d103c0 98 FUNC GLOBAL DEFAULT 17 realloc@@GLIBC_2.2.5
The executable is first in the global symbol lookup scope, so glibc's own internal calls to free now bind to mimalloc inside the claude binary.
2. The interposed free has no NULL check. Disassembly of free in 2.1.242:
0000000001d10430 <free>:
1d10430: push %rbp
1d10431: mov %rsp,%rbp
1d10434: mov %rdi,%rsi
1d10437: mov 0x3781982(%rip),%rax # segment map base
1d1043e: mov %rdi,%rcx
1d10441: shr $0x1d,%rcx # ptr >> 29
1d10445: mov 0x80(%rax,%rcx,8),%rax # rax = map[ptr >> 29] -> NULL for ptr==0
1d1044d: mov %esi,%ecx
1d1044f: shr $0xd,%ecx
1d10452: and $0xfff8,%ecx
1d10458: mov (%rax,%rcx,1),%rdi # <-- SIGSEGV, rax=0, rcx=0 => load from 0x0
With ptr == NULL: rcx = 0, the segment-map slot is NULL, and the next load dereferences address 0. No null guard anywhere on this path.
3. The faulting call really is free(NULL). Breaking on free shows the first call in the process is null, and it is the one that crashes:
$ gdb -batch -ex 'break free' -ex 'run --version' -ex 'printf "free arg #1 = %p\n", $rdi' -ex continue --args 2.1.242 --version
Breakpoint 1.1, 0x0000000001d10434 in free ()
free arg #1 = (nil)
Program received signal SIGSEGV, Segmentation fault.
0x0000000001d10458 in free ()
Register state at the fault corroborates it: rax=0, and rsi=0 — rsi is the preserved copy of the incoming argument made at free+4 and never rewritten before the fault.
Ruled out
- Not locale-dependent.
LC_ALL=C LANG=Ccrashes identically.newlocaleis simply the first libc path to hitfree(NULL). - Not a corrupt or truncated download. Valid ELF,
lddresolves cleanly, binary is not stripped. - Not a glibc regression. glibc 2.44 has been installed since 2026-08-12; 2.1.241 was installed 2026-08-23 and works fine on that same glibc.
- Not launcher/wrapper related. Executing the versioned binary directly reproduces it.
Environment
- Claude Code 2.1.242, native install (
~/.local/share/claude/versions/2.1.242) BUILD_TIME: 2026-08-24T17:00:10Z,GIT_SHA: 2482ce9083708842d832441fde1721626f306157- Binary size jumped 342,636,848 -> 377,568,472 bytes vs 2.1.241
- Arch Linux, kernel 7.1.8-zen1-3-zen x86_64
- glibc 2.44 (
glibc 2.44+r24+g16be1518495f-1) - AMD Ryzen 7 5800X3D
Impact
The CLI cannot start at all, which also means it cannot auto-update out of the broken state. Recovery requires manually repointing ~/.local/bin/claude at an older version:
ln -sfn ~/.local/share/claude/versions/2.1.241 ~/.local/bin/claude
Suggested fix
Add the standard if (p == NULL) return; guard to the exported free (and check realloc(NULL, n) on the same interposition path), or stop exporting versioned glibc allocator symbols so mimalloc is only used for the runtime's own allocations.
Possibly related pre-main startup regression from the same area: #87583.
16 Comments
Confirming on CachyOS: 2.1.242 segfaults on every launch including
--version(also reproduced with a freshinstall.shdownload); 2.1.241 works on the same system. Diagnostics below.Environment
Installed binaries
Crash reproduction
gdb: first free() call is NULL and faults in interposed free
readelf: allocator symbols exported by each build
Recovery
Confirming the workaround in the report: repointing
~/.local/bin/claudeto 2.1.241 and settingDISABLE_AUTOUPDATER=1in~/.claude/settings.jsonrestores a working CLI.Same issue for me. Downgrading to 2.1.241 worked.
Also hit on Arch Linux — same build, same fault address. Adding one thing that isn't in the thread yet: how the update itself was recorded.
Environment
Confirming the report
Identical artifact to the one already analysed here —
BUILD_TIME:"2026-08-24T17:00:10Z",GIT_SHA:"2482ce9083708842d832441fde1721626f306157", 377568472 bytes, sha256528ef039aa7d64d7b3fbc06925132755a516b4dcaad784cf0b51fe03167360d4.2.1.242 --versionfaults at0x1d10458 in free ()withrdi = 0, and the allocator symbols are exported by 2.1.242 but not by 2.1.241, matching thereadelf --dyn-symsdiff above. The ELF is intact (section header table ends exactly at EOF,symtabreadable), so this is not a truncated download — the shipped artifact does not start.The swap was recorded as a success
~/.claude/.last-update-result.jsonafter the update:Timeline (UTC):
There is no successful launch anywhere in that window — it faults every time,
--versionincluded. (The most recent of those 21 cores is my own reproduction run while investigating, not a normal invocation.)So from the client's point of view the update succeeded and
~/.local/bin/claudewas left pointing at a binary that cannot execute. Recovery was manual: repointing~/.local/bin/claudeat~/.local/share/claude/versions/2.1.241.Prior report
#69980 described the same swap-without-validation behaviour in June (2.1.176 on RHEL 7, SIGILL instead of SIGSEGV). It was closed as not planned and carries the
stalelabel. Linking it only because the same failure mode recurred here with a different root cause underneath — whether updater-side validation is in scope is your call, and I'm not trying to re-open that thread from the outside.2.1.243 still has this — the
latestchannel briefly rolled back to 2.1.241, then moved forward to 2.1.243, which reintroduces the identical defect. Machines that recovered by repointing the symlink are being auto-updated straight back into the crash.2.1.243 is a genuinely different build, not a re-tag of 2.1.242:
Both are 377,568,472 bytes, and 2.1.243 exports the same six versioned glibc allocator symbols at the same addresses:
Crashes identically:
Three SIGSEGV coredumps for the 2.1.243 binary within four minutes of it being installed, all on plain
--version.Note that
readelf --dyn-syms $BINARY | grep -c 'free@@GLIBC'returning nonzero is a reliable pre-launch check for whether a given build is affected — useful since the CLI can't start to tell you itself.Environment
Adds an Intel data point; the report and the first confirmation are both AMD Ryzen, so this isn't CPU-vendor-specific.
2.1.241 works on the same machine.
Note on the workaround
Repointing
~/.local/bin/claudeat 2.1.241 is not sufficient on its own — the auto-updater moves the symlink back tolateston next launch.DISABLE_AUTOUPDATER=1in~/.claude/settings.jsonis currently required to stay on a working build, and there's no supported way to follow thestablechannel instead (the updater fetches/claude-code-releases/latestunconditionally;releaseChannelin the binary is telemetry metadata, not a settings key).Reproduced on Arch Linux / CachyOS kernel with glibc 2.44.
~/.local/share/claude/versions/2.1.243)claude --versionsegfaults deterministically; 2.1.239/2.1.241 work fineLANG=C LC_ALL=CCoredump stack trace (
coredumpctl info):Matches the interposed-mimalloc-
free/newlocaletheory above. Workaround: symlink back to versions/2.1.241.Still present in 2.1.243 (native install, Linux x64) — the 243 build ships the same interposed allocator at the same addresses, so nothing changed between 242 and 243 here.
Confirming both of your findings against the 2.1.243 binary:
1. Allocator symbols still exported, at addresses identical to your 2.1.242 dump:
2. The interposed
freestill has no NULL check on the incoming pointer:The
test %rdi,%rdiat1d1045cguards the result of the segment-map load, one load too late to help a NULL argument.My coredump's faulting PC is exactly that instruction:
Environment: Arch Linux, kernel 7.1.8-zen1-3-zen, glibc 2.44, AMD Ryzen 7 7735HS (AVX2 present, so not the baseline-build class of crash).
Version matrix on this machine (native installs, invoked directly as
<binary> --version):Ruled out a damaged download:
claude install 2.1.243 --forcere-fetched a byte-identical binary (md5fd2f13ae50f4464dfa569d7b85a839e5) that still crashes. Also unaffected by locale (LC_ALL=C,C.UTF-8,en_US.UTF-8, andenv -iall crash identically), byLD_BIND_NOW=1, and byGLIBC_TUNABLES=glibc.malloc.tcache_count=0.Workaround for anyone landing here: repoint
~/.local/bin/claudeat~/.local/share/claude/versions/2.1.241and setDISABLE_AUTOUPDATER=1, sinceclaude install <version>silently repoints that symlink back.Cross-linking from #89368 (v2.1.243, same defect): I bisected this across glibc versions with one and the same binary, and the switch is glibc, not the distribution.
Released glibc 2.36, 2.39, 2.40, 2.41, 2.42 and 2.43 all run
claude --versionsuccessfully (Debian 12, Ubuntu 24.04 / 24.10 / 25.04 / 25.10 / 26.04, Fedora 44, openSUSE Tumbleweed). glibc 2.43.9000 (Fedora Rawhide) and 2.44 (Arch Linux) both SIGSEGV. Fedora Rawhide shares no packaging with Arch or Manjaro, so the boundary is in upstream glibc, between the 2.43 release and the development series that became 2.44.Registers confirm the diagnosis in the title:
%rdiis 0 on entry tofree, so it isfree(NULL). The segment lookup for address 0 returns NULL atfree+21and is dereferenced without a check atfree+40.Full version table, disassembly and
.dynsymoutput are in #89368.so they removed 2.1.242 due to this issue and reintroduced the segfault in 2.1.243 what????
Confirming on two more machines, with the glibc source line for the offending
free(NULL).Environment (both hosts)
linux 7.1.9.arch1-2) and CachyOS (linux-cachyos 7.2.0-1), x86-64, AMD Zen 3 / Zen 3+ (AVX2, no AVX-512)2.44+r24+g16be1518495f-1— byte-identicallibc.so.6on bothversions/2.1.243sha2564b0dafeedd0b469c41988e200036fd773e7553ba960349c9f02a82c6d1f2ba27(matches #89369)--version; 2.1.241 / 2.1.239 run fine on the same hostsLANG=C,LC_ALL=C,env -i), ASLR (setarch -R), sysctls, download corruption, glibc package integrity (pacman -Qkkclean)Trace (
coredumpctl info,Signal: 11 (SEGV) si_code: SEGV_MAPERR, kernel:segfault at 0 ip 0000000001d10458 … error 4)The call and the source line. Breakpoint on
newlocaleunder gdb shows the only call before the crash isResolving
libc.so.6+0x3530avia Arch debuginfod:So glibc 2.44's
newlocaleunconditionally freestmp_buffer, which is NULL for the plain"C"locale — thefree(NULL)is legitimate and required to be a no-op.Why the interposed
freedies. Disassembly at the fault:At the time of the call the page map's entry for address 0 is still NULL (
x/4gxof the map shows all zeros), so the lookup forp == NULLderefs NULL. Either ap == NULLearly-return or installing the NULL sub-map before anything can callfreefixes it.Two operational notes for anyone landing here
claude install 2.1.241prints "successfully installed 2.1.241" but leaves~/.local/bin/claudepointing at 2.1.243.ln -sfn ~/.local/share/claude/versions/2.1.241 ~/.local/bin/claudeis what actually rolls back."env": {"DISABLE_AUTOUPDATER": "1"}in~/.claude/settings.jsonstops that for new sessions.Confirming 2.1.243 on CachyOS — glibc 2.44+r24+g16be1518495f-1, kernel 7.2.0-1-cachyos, Ryzen (x86_64). Identical fault address on every invocation including
--version. Artifact sha2564b0dafeedd0b469c41988e200036fd773e7553ba960349c9f02a82c6d1f2ba27(377,568,472 bytes, matches the GIT_SHA/BUILD_TIME posted above for 2.1.243).Two data points I don't see in the thread yet:
1. Not environment- or config-dependent. Crashes identically under a scrubbed environment with an empty HOME:
so
~/.claude.json/ user config can be ruled out.2. Full stack for the free(NULL). coredumpctl shows it comes from glibc's
__newlocaleinside apthread_oncestatic-init chain — i.e. C-runtime initialization, before main and before any JS runs, which is why no CLI argument avoids it:Version-range: 2.1.240 (342,636,848 bytes) has no allocator exports in
.dynsymand works, consistent with 2.1.241-good / 2.1.242-first-bad established above.Workaround confirmed here too: repoint
~/.local/bin/claudeat a pre-2.1.242 build +DISABLE_AUTOUPDATER=1, after the auto-updater had silently swapped a manually downgraded install back to 2.1.243 (recorded as"outcome":"success"in.last-update-result.json, same as reported above).Confirming on CachyOS (Arch-based), glibc 2.44+r24, Intel Core Ultra 7 256V (Lunar Lake). Same bisect result, fetching binaries straight from downloads.claude.ai: 2.1.241 runs, 2.1.242 and 2.1.243 segfault on
--version, 100% of launches.Two data points to add:
The crash is kernel-independent. Reproduced identically on linux-cachyos 7.1.5, 7.2.0, and the 6.18.42 LTS kernel, same glibc.
Caught the faulting call live in gdb on 2.1.243, breakpoint on
__newlocale. It is the first locale call glibc makes during static init:then SIGSEGV in the interposed
free, at the segment-map load with rax already masked to 0:One note for anyone else debugging this: install.sh always resolves
latest(line 149; the version argument only reaches the binary's own install step, which never runs because it crashes first), so "I installed an older version and it still crashed" does not test what it appears to. To actually pin, pull the binary directly:and run with
DISABLE_AUTOUPDATER=1.Confirming this on 2.1.243 as well (same defect, one version later), with two data points that may help: a same-machine/two-glibc split, and the disassembly showing the missing NULL guard.
Same binary, same machine, two libcs
My
$HOMEis bind-mounted into a container, so this is byte-for-byte the same executable in both runs — the only variable is the libc:| | glibc |
2.1.243 --version||---|---|---|
| host (Ubuntu) | 2.43 | OK |
| container (Fedora 45) | 2.44 | SIGSEGV, rc=139 |
Version sweep inside the 2.44 container:
Exported allocator symbols, matching #89389 —
2.1.240: 0,2.1.241: 0,2.1.243: 4 (malloc,free,calloc,realloc).The interposed
freehas no NULL check on entryBacktrace (gdb, in-container):
newlocale.c:291isfree (tmp_buffer);on the success path, wheretmp_bufferis NULL because no temporary was needed. C requiresfree(NULL)to be a no-op. Disassembling the bundledfreein 2.1.243 shows it isn't:free+40 = 0x1d10458, exactly the faulting pc in the backtrace. There is no check of the incoming pointer — it goes straight into a two-level page-map lookup, and the level-1 slot covering address 0 is empty, so it dereferences NULL. Thetestat+44guards the result of that load, not the argument.So this is a defect in the build independent of libc version, not a glibc 2.44 / Fedora / Arch compatibility problem. glibc 2.43 simply doesn't reach that particular
free(NULL)during static init, which is exactly why the same binary is fine on the host and dies in the container.Not container- or image-specific
Identical SIGSEGV in three unrelated bare base images with nothing but the base image and the bind-mounted binary:
Workaround note
Pinning to 2.1.241 needs
DISABLE_AUTOUPDATER=1to stick when$HOMEis shared between host and container —~/.local/bin/claudeis the same symlink on both sides, so an update on either side walks it back to the broken version.LD_PRELOADis not a workaround: an executable's own symbol definitions beat any preloaded library.Confirming this is still present in 2.1.243 (this issue is titled 2.1.242), on Arch Linux, kernel 7.1.5-arch1-2, glibc 2.44, x86_64. Native installer. 2.1.241 is fine, 2.1.243 crashes 100% of the time including
claude --version.Same faulting instruction pointer as your backtrace's frame
#0 free () 0x0000000001d10458:error 4= user-mode read of an unmapped page, fault address exactly0. File offset of the faulting instruction is0x1b0f458(the segment is mapped at file offset0x1752000, which is whyIP - VMA_start = 0x3bd458differs).24 crashes recorded across 14 different CPU cores, all at the identical IP — fully deterministic, not a race. Deleting the binary and letting the updater re-download gives a byte-identical file (md5
fd2f13ae50f4464dfa569d7b85a839e5), so it is not a corrupted download.Disassembly of the missing NULL check
The kernel log's
Code:bytes decode to the top of the interposedfree, and show exactly where the missingfree(NULL)guard bites:This is mimalloc's two-level radix lookup from pointer to owning segment. The effective address is
rax + rcx;rcxis masked to0xfff8so both terms are non-negative, meaning the sum can only be0if both are0.rcx == 0requires bits 13..28 of the incoming pointer to be zero, andrax == 0means the first-level slot indexed byptr >> 29is NULL. The simplest input satisfying both isrdi == 0— i.e.free(NULL), exactly as you diagnosed from thenewlocaleframe.Note the asymmetry: the code null-checks the result of the second level (
test rdi, rdi/je) but never the first. So the fix is a NULL check on the incoming pointer (or on the first-level load) at the very top of the interposedfree.Workaround confirmed: pin to 2.1.241 and disable the auto-updater.
Ehh should we post to mimalloc so this issue could be addressed?
Fixed in 2.1.245.
From the changelog:
Verified on the host that reproduced it
Same machine as my earlier comment (Arch Linux, kernel 7.1.9-arch1-2, glibc 2.44, Ryzen 7 8700G), same environment, only the binary differs:
| version |
--version||---|---|
| 2.1.243 | SIGSEGV,
rc=139, no output || 2.1.245 |
rc=0, prints2.1.245 (Claude Code)— three consecutive runs |Past
--version: 2.1.245 starts the TUI, reads its settings and connects its MCP servers. This comment is written from a session running on it.The interposition is gone
Artifact: linux-x64, 391948592 bytes, sha256
16ad2b94deaf7b29abed966d981c9991a47af0420f5be8ed4a3f83bea9f678bc, matching the published manifest for 2.1.245.readelf --dyn-syms -Wacross the three versions I have locally:| version |
.dynsymentries | allocator symbols exported ||---|---|---|
| 2.1.241 | 1196 | none |
| 2.1.243 | 1224 | 4 |
| 2.1.245 | 1196 | none |
What 2.1.243 exported, for reference:
2.1.245 exports none of them, and its
.dynsymcount is back to the 2.1.241 figure. The ELF type is stillEXEC(non-PIE) in all three, so the fix is on the export side rather than a switch to PIE: nothing overrides glibc's allocator any more, so thefree(NULL)that__newlocaleperforms during static init reaches libc's ownfree, which returns without touching address 0.Checking a local copy
The symbol names carry a version suffix, so an exact-match filter silently reports nothing on a broken build too:
No output means the build does not interpose.
If you are still on a crashing version
latestnow serves 2.1.245. Thestablechannel is a separate pointer and still reads 2.1.231, so an installation pinned tostablewill not pick the fix up on its own.Confirming this is still present in 2.1.243, and adding a second distro data point plus two pieces of evidence the original doesn't include.
Environment
2.44+r24+g16be1518495f-1(2.44 installed 2026-08-10, so it predates 2.1.241, which works)~/.local/share/claude/versions/2.1.243, Bun v1.4.0 embedded in both 2.1.241 and 2.1.243Same backtrace, same addresses as the OP (
freeat0x1d10430, fault atfree+40,rax=rcx=rdi=rsi=0).readelf --dyn-symsshowsmalloc@@GLIBC_2.2.5/free@@GLIBC_2.2.5exported in 2.1.243 and absent in 2.1.241.LC_ALL=CandLC_ALL=C.UTF-8crash identically.1. The exact glibc call site (with glibc debug symbols via debuginfod):
tmp_bufferis the scratch buffer glibc 2.44'snewlocaleonly allocates when it has to normalise the locale name; for the"C"request made during Bun static init it stays NULL, so this is a plainfree(NULL).2. Direct proof of the interposition, from the core
The call is glibc's internal GOT call (
-fno-plt):i.e. libc's own GOT slot for
freeis bound to the mimallocfreeinside the claude binary.Full chain:
strace confirms nothing but ld.so library mapping happens before the SIGSEGV — no config, argv, network, or
~/.claudeinvolvement.Likely why CI misses it: glibc 2.39 (Ubuntu 24.04) doesn't hit a
free(NULL)on this path, so only distros on 2.44 (Arch/CachyOS/Fedora Rawhide…) see it. Anyfree(NULL)from inside libc would trigger it though; it's only a matter of which glibc version calls it first.Workaround as in OP:
ln -sfn ~/.local/share/claude/versions/2.1.241 ~/.local/bin/claude+"DISABLE_AUTOUPDATER": "1".