[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

Status Open
Reported on v2.1.239
Maintainer reply None cached
Activity 16 comments · opened Aug 24, 2026

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=0rsi 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=C crashes identically. newlocale is simply the first libc path to hit free(NULL).
  • Not a corrupt or truncated download. Valid ELF, ldd resolves 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.

View original on GitHub ↗

16 Comments

cmoro-deusto · 5 days ago

Confirming on CachyOS: 2.1.242 segfaults on every launch including --version (also reproduced with a fresh install.sh download); 2.1.241 works on the same system. Diagnostics below.

Environment

$ cat /etc/os-release (name/version)
NAME="CachyOS Linux"
BUILD_ID=rolling

$ uname -rm
7.2.0-1-cachyos x86_64

$ ldd --version
ldd (GNU libc) 2.44

$ lscpu (model)
Model name:  AMD Ryzen 9 7950X 16-Core Processor

Installed binaries

$ ls -l /home/dordo/.local/share/claude/versions
total 703332
-rwxr-xr-x 1 dordo dordo 342636848 ago 23 20:36 2.1.241
-rwxr-xr-x 1 dordo dordo 377568472 ago 24 21:55 2.1.242
$ strings 2.1.242 | grep -oE '(BUILD_TIME|GIT_SHA|VERSION):"[^"]*"' | sort -u
BUILD_TIME:"2026-08-24T17:00:10Z"
GIT_SHA:"2482ce9083708842d832441fde1721626f306157"
VERSION:"2.0.0"
VERSION:"2020-06-01"
VERSION:"2.1.242"
VERSION:"api-version"
VERSION:"dev"
VERSION:"unknown"
VERSION:"x-ms-httpver"

Crash reproduction

$ /home/dordo/.local/share/claude/versions/2.1.241 --version
2.1.241 (Claude Code)
exit=0
$ /home/dordo/.local/share/claude/versions/2.1.242 --version
./cc-89334-report-v3.sh: line 78:  9971 Segmentation fault         (core dumped) ( "$BAD" --version ) 2>&1
exit=139   # 139 = 128+SIGSEGV

gdb: first free() call is NULL and faults in interposed free

$ gdb -batch -ex 'break free' -ex run -ex 'printf "free arg = %p\n", $rdi' -ex continue --args 2.1.242 --version

This GDB supports auto-downloading debuginfo from the following URLs:
  <https://debuginfod.archlinux.org>
  <https://debuginfod.cachyos.org>
Enable debuginfod for this session? (y or [n]) [answered N; input not from terminal]
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
Breakpoint 1 at 0x1d10434
Using host libthread_db library "/usr/lib/libthread_db.so.1".

Breakpoint 1.1, 0x0000000001d10434 in free ()
free arg = (nil)

Program received signal SIGSEGV, Segmentation fault.
0x0000000001d10458 in free ()

readelf: allocator symbols exported by each build

$ readelf --dyn-syms --wide 2.1.241 | grep allocator symbols
(no output)
$ readelf --dyn-syms --wide 2.1.242 | grep allocator symbols
   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

Recovery

Confirming the workaround in the report: repointing ~/.local/bin/claude to 2.1.241 and setting DISABLE_AUTOUPDATER=1 in ~/.claude/settings.json restores a working CLI.

trickpattyFH20 · 5 days ago

Same issue for me. Downgrading to 2.1.241 worked.

kay-ws · 5 days ago

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

NAME="Arch Linux"   BUILD_ID=rolling
$ uname -rm        7.1.9-arch1-2 x86_64
$ ldd --version    ldd (GNU libc) 2.44
Model name:        AMD Ryzen 7 8700G w/ Radeon 780M Graphics   (avx2 present)

Confirming the report

Identical artifact to the one already analysed here — BUILD_TIME:"2026-08-24T17:00:10Z", GIT_SHA:"2482ce9083708842d832441fde1721626f306157", 377568472 bytes, sha256 528ef039aa7d64d7b3fbc06925132755a516b4dcaad784cf0b51fe03167360d4.

2.1.242 --version faults at 0x1d10458 in free () with rdi = 0, and the allocator symbols are exported by 2.1.242 but not by 2.1.241, matching the readelf --dyn-syms diff above. The ELF is intact (section header table ends exactly at EOF, symtab readable), so this is not a truncated download — the shipped artifact does not start.

The swap was recorded as a success

~/.claude/.last-update-result.json after the update:

{"timestamp":"2026-08-24T20:12:33.180Z","path":"native","outcome":"success","status":"success","version_from":"2.1.241","version_to":"2.1.242","error_code":null}

Timeline (UTC):

20:12:33  2.1.242 written to ~/.local/share/claude/versions/, recorded as "outcome":"success"
20:13:32  first SIGSEGV (59 s later)
          ... 21 cores recorded for 2.1.242

There is no successful launch anywhere in that window — it faults every time, --version included. (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/claude was left pointing at a binary that cannot execute. Recovery was manual: repointing ~/.local/bin/claude at ~/.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 stale label. 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.

trickpattyFH20 · 5 days ago

2.1.243 still has this — the latest channel 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:

2.1.242: GIT_SHA 2482ce9083708842d832441fde1721626f306157  BUILD_TIME 2026-08-24T17:00:10Z
2.1.243: GIT_SHA 8565f923a3ec61dc4c61bf7bfd995521c702c9fc  BUILD_TIME 2026-08-24T21:05:22Z

Both are 377,568,472 bytes, and 2.1.243 exports the same six versioned glibc allocator symbols at the same addresses:

$ readelf --dyn-syms --wide 2.1.243 | grep -E '\b(free|malloc|calloc|realloc|aligned_alloc|posix_memalign)@'
   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

Crashes identically:

$ ~/.local/share/claude/versions/2.1.243 --version
[1]  37726 segmentation fault (core dumped)
$ echo $?
139

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.

CachyOS Linux (rolling)
kernel 7.2.0-1-cachyos x86_64
glibc 2.44
Intel Core i9-10900K

2.1.241 works on the same machine.

Note on the workaround

Repointing ~/.local/bin/claude at 2.1.241 is not sufficient on its own — the auto-updater moves the symlink back to latest on next launch. DISABLE_AUTOUPDATER=1 in ~/.claude/settings.json is currently required to stay on a working build, and there's no supported way to follow the stable channel instead (the updater fetches /claude-code-releases/latest unconditionally; releaseChannel in the binary is telemetry metadata, not a settings key).

raiu698 · 5 days ago

Reproduced on Arch Linux / CachyOS kernel with glibc 2.44.

  • Version: 2.1.243 native build (~/.local/share/claude/versions/2.1.243)
  • claude --version segfaults deterministically; 2.1.239/2.1.241 work fine
  • Reproduces even with LANG=C LC_ALL=C

Coredump stack trace (coredumpctl info):

#0  0x0000000001d10458 free (2.1.243 + 0x1b10458)
#1  0x00007f8e13a3a1ab __newlocale (libc.so.6 + 0x3a1ab)
#2  0x0000000001abd282 n/a (2.1.243 + 0x18bd282)
#3  0x00007f8e13ab2584 n/a (libc.so.6 + 0xb2584)
#4  0x00007f8e13ab26a9 pthread_once (libc.so.6 + 0xb26a9)
#5  0x0000000001abc2d2 n/a

Matches the interposed-mimalloc-free/newlocale theory above. Workaround: symlink back to versions/2.1.241.

myghi63 · 5 days ago

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:

$ readelf --dyn-syms --wide 2.1.241 | grep -E '\b(free|malloc|calloc|realloc|aligned_alloc|posix_memalign)@'
(no output)

$ readelf --dyn-syms --wide 2.1.243 | grep -E '\b(free|malloc|calloc|realloc|aligned_alloc|posix_memalign)@'
   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

2. The interposed free still has no NULL check on the incoming pointer:

$ objdump -d --start-address=0x1d10430 --stop-address=0x1d10470 2.1.243
 1d1043e: mov    %rdi,%rcx
 1d10441: shr    $0x1d,%rcx
 1d10445: mov    0x80(%rax,%rcx,8),%rax   # map[ptr >> 29] -> NULL when ptr == 0
 1d1044d: mov    %esi,%ecx
 1d1044f: shr    $0xd,%ecx
 1d10452: and    $0xfff8,%ecx
 1d10458: mov    (%rax,%rcx,1),%rdi       # <-- SIGSEGV
 1d1045c: test   %rdi,%rdi
 1d1045f: je     1d1047f

The test %rdi,%rdi at 1d1045c guards 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:

Signal: 11 (SEGV) si_code: SEGV_MAPERR
#0  0x0000000001d10458 free           (2.1.243 + 0x1b10458)
#1  0x00007f744463530a __newlocale    (libc.so.6 + 0x3530a)
#2  0x0000000001abd282 n/a            (2.1.243 + 0x18bd282)
#3  0x00007f744469d7fc n/a            (libc.so.6 + 0x9d7fc)
#4  0x00007f744469d879 pthread_once   (libc.so.6 + 0x9d879)
...
#16 0x00007f7444627892 __libc_start_main (libc.so.6 + 0x27892)

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):

2.1.239: exit=0
2.1.241: exit=0
2.1.243: exit=139 (SIGSEGV)

Ruled out a damaged download: claude install 2.1.243 --force re-fetched a byte-identical binary (md5 fd2f13ae50f4464dfa569d7b85a839e5) that still crashes. Also unaffected by locale (LC_ALL=C, C.UTF-8, en_US.UTF-8, and env -i all crash identically), by LD_BIND_NOW=1, and by GLIBC_TUNABLES=glibc.malloc.tcache_count=0.

Workaround for anyone landing here: repoint ~/.local/bin/claude at ~/.local/share/claude/versions/2.1.241 and set DISABLE_AUTOUPDATER=1, since claude install <version> silently repoints that symlink back.

kay-ws · 5 days ago

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 --version successfully (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: %rdi is 0 on entry to free, so it is free(NULL). The segment lookup for address 0 returns NULL at free+21 and is dereferenced without a check at free+40.

Full version table, disassembly and .dynsym output are in #89368.

jgyy · 5 days ago

so they removed 2.1.242 due to this issue and reintroduced the segfault in 2.1.243 what????

dadams-AU · 5 days ago

Confirming on two more machines, with the glibc source line for the offending free(NULL).

Environment (both hosts)

  • Arch Linux (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)
  • glibc 2.44+r24+g16be1518495f-1 — byte-identical libc.so.6 on both
  • Native install; versions/2.1.243 sha256 4b0dafeedd0b469c41988e200036fd773e7553ba960349c9f02a82c6d1f2ba27 (matches #89369)
  • 2.1.243 segfaults 100% on --version; 2.1.241 / 2.1.239 run fine on the same hosts
  • Ruled out: locale (LANG=C, LC_ALL=C, env -i), ASLR (setarch -R), sysctls, download corruption, glibc package integrity (pacman -Qkk clean)

Trace (coredumpctl info, Signal: 11 (SEGV) si_code: SEGV_MAPERR, kernel: segfault at 0 ip 0000000001d10458 … error 4)

#0  0x0000000001d10458 free (2.1.243 + 0x1b10458)
#1  0x00007eff8d83530a __newlocale (libc.so.6 + 0x3530a)
#2  0x0000000001abd282 n/a (2.1.243 + 0x18bd282)
#3  0x00007eff8d89d7fc n/a (libc.so.6 + 0x9d7fc)        # __pthread_once_slow, pthread_once.c:118
#4  0x00007eff8d89d879 pthread_once (libc.so.6 + 0x9d879)
#5  0x0000000001abc2d2 n/a (2.1.243 + 0x18bc2d2)
…
#16 0x00007eff8d827892 __libc_start_main (libc.so.6 + 0x27892)

The call and the source line. Breakpoint on newlocale under gdb shows the only call before the crash is

newlocale(mask=64 /* 1<<LC_ALL */, name="C", base=NULL)

Resolving libc.so.6+0x3530a via Arch debuginfod:

Line 293 of "newlocale.c" starts at address 0x3530a <__newlocale+58>
288	  const locale_t result = __newlocale_1 (category_mask, locale,
289	                                         base, &tmp_buffer);
290
291	  free (tmp_buffer);
292
293	  return result;

So glibc 2.44's newlocale unconditionally frees tmp_buffer, which is NULL for the plain "C" locale — the free(NULL) is legitimate and required to be a no-op.

Why the interposed free dies. Disassembly at the fault:

<free+7>:   mov    0x3781982(%rip),%rax   # page-map global @ 0x5491dc0
<free+17>:  shr    $0x1d,%rcx             # ptr >> 29 → top-level index
<free+21>:  mov    0x80(%rax,%rcx,8),%rax  # sub-map for that range → NULL for ptr=0
<free+40>:  mov    (%rax,%rcx,1),%rdi      # <== SIGSEGV, rax=0

At the time of the call the page map's entry for address 0 is still NULL (x/4gx of the map shows all zeros), so the lookup for p == NULL derefs NULL. Either a p == NULL early-return or installing the NULL sub-map before anything can call free fixes it.

Two operational notes for anyone landing here

  • claude install 2.1.241 prints "successfully installed 2.1.241" but leaves ~/.local/bin/claude pointing at 2.1.243. ln -sfn ~/.local/share/claude/versions/2.1.241 ~/.local/bin/claude is what actually rolls back.
  • Running sessions on 2.1.241 keep working and will keep re-downloading 2.1.243 and re-pointing the symlink; "env": {"DISABLE_AUTOUPDATER": "1"} in ~/.claude/settings.json stops that for new sessions.
Selutha · 5 days ago

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 sha256 4b0dafeedd0b469c41988e200036fd773e7553ba960349c9f02a82c6d1f2ba27 (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:

$ env -i HOME=/tmp/fakehome PATH=/usr/bin TERM=dumb ~/.local/share/claude/versions/2.1.243 --version
Segmentation fault (core dumped)   # exit 139

so ~/.claude.json / user config can be ruled out.

2. Full stack for the free(NULL). coredumpctl shows it comes from glibc's __newlocale inside a pthread_once static-init chain — i.e. C-runtime initialization, before main and before any JS runs, which is why no CLI argument avoids it:

#0  free                 (2.1.243 + 0x1b10458)
#1  __newlocale          (libc.so.6)
#2  0x18bd282            (2.1.243)
#3  0xb2cc4              (libc.so.6)
#4  pthread_once         (libc.so.6)
...
#16 __libc_start_main    (libc.so.6)
#17 0x1753a2e            (2.1.243)

Version-range: 2.1.240 (342,636,848 bytes) has no allocator exports in .dynsym and works, consistent with 2.1.241-good / 2.1.242-first-bad established above.

Workaround confirmed here too: repoint ~/.local/bin/claude at 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).

cjs0131 · 5 days ago

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:

newlocale(mask=0x40, name="C", base=NULL)

then SIGSEGV in the interposed free, at the segment-map load with rax already masked to 0:

=> 0x1d10458 <free+40>: mov (%rax,%rcx,1),%rdi   # rax = 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:

curl -fsSL https://downloads.claude.ai/claude-code-releases/2.1.241/linux-x64/claude -o claude && chmod +x claude

and run with DISABLE_AUTOUPDATER=1.

JasonDictos · 5 days ago

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 $HOME is 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:

2.1.240 -> rc=0
2.1.241 -> rc=0
2.1.243 -> SIGSEGV (139)

Exported allocator symbols, matching #89389 — 2.1.240: 0, 2.1.241: 0, 2.1.243: 4 (malloc, free, calloc, realloc).

The interposed free has no NULL check on entry

Backtrace (gdb, in-container):

Program received signal SIGSEGV
#0  0x0000000001d10458 in free ()
#1  0x00007ffff7e3edf9 in __newlocale (...) at newlocale.c:291
#3  __pthread_once_slow ...
#16 __libc_start_main_impl

newlocale.c:291 is free (tmp_buffer); on the success path, where tmp_buffer is NULL because no temporary was needed. C requires free(NULL) to be a no-op. Disassembling the bundled free in 2.1.243 shows it isn't:

0000000001d10430 <__libc_cfree>:
 1d10430:  push   %rbp
 1d10434:  mov    %rdi,%rsi                    ; preserve the argument (NULL)
 1d10437:  mov    0x3781982(%rip),%rax         ; page-map base
 1d10441:  shr    $0x1d,%rcx                   ; ptr >> 29        -> 0
 1d10445:  mov    0x80(%rax,%rcx,8),%rax       ; level-1 slot for address 0 -> NULL
 1d1044f:  shr    $0xd,%ecx                    ; ptr >> 13        -> 0
 1d10458:  mov    (%rax,%rcx,1),%rdi           ; deref NULL       <-- SIGSEGV
 1d1045c:  test   %rdi,%rdi                    ; the only NULL test, on the *loaded entry*
 1d1045f:  je     1d1047f

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. The test at +44 guards 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:

$ for tag in 9.2.3 9.1.0 9.2.0; do
    docker run --rm -v "$HOME/.local/share/claude/versions/2.1.243:/claude:ro" \
      --entrypoint /bin/bash <image>:$tag -c '/claude --version >/dev/null 2>&1; echo rc=$?'
  done
rc=139
rc=139
rc=139

Workaround note

Pinning to 2.1.241 needs DISABLE_AUTOUPDATER=1 to stick when $HOME is shared between host and container — ~/.local/bin/claude is the same symlink on both sides, so an update on either side walks it back to the broken version. LD_PRELOAD is not a workaround: an executable's own symbol definitions beat any preloaded library.

HectorHuaripata · 5 days ago

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:

kernel: claude[130932]: segfault at 0 ip 0000000001d10458 sp 00007fffd8b8c000 error 4 in 2.1.243[1b0f458,1953000+3b33000] likely on CPU 10 (core 10, socket 0)

error 4 = user-mode read of an unmapped page, fault address exactly 0. File offset of the faulting instruction is 0x1b0f458 (the segment is mapped at file offset 0x1752000, which is why IP - VMA_start = 0x3bd458 differs).

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 interposed free, and show exactly where the missing free(NULL) guard bites:

55                          push  rbp
48 89 e5                    mov   rbp, rsp
48 89 fe                    mov   rsi, rdi
48 8b 05 82 19 78 03        mov   rax, [rip+0x3781982]    ; segment-map base
48 89 f9                    mov   rcx, rdi
48 c1 e9 1d                 shr   rcx, 0x1d               ; ptr >> 29
48 8b 84 c8 80 00 00 00     mov   rax, [rax+rcx*8+0x80]   ; first level  (NOT checked)
89 f1                       mov   ecx, esi
c1 e9 0d                    shr   ecx, 0xd                ; ptr >> 13
81 e1 f8 ff 00 00           and   ecx, 0xfff8             ; second-level index
<48> 8b 3c 08               mov   rdi, [rax+rcx]          ; <-- FAULTS
48 85 ff                    test  rdi, rdi                ; second level IS checked
74 1e                       je    ...

This is mimalloc's two-level radix lookup from pointer to owning segment. The effective address is rax + rcx; rcx is masked to 0xfff8 so both terms are non-negative, meaning the sum can only be 0 if both are 0. rcx == 0 requires bits 13..28 of the incoming pointer to be zero, and rax == 0 means the first-level slot indexed by ptr >> 29 is NULL. The simplest input satisfying both is rdi == 0 — i.e. free(NULL), exactly as you diagnosed from the newlocale frame.

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 interposed free.

Workaround confirmed: pin to 2.1.241 and disable the auto-updater.

Ricepies · 5 days ago

Ehh should we post to mimalloc so this issue could be addressed?

kay-ws · 5 days ago

Fixed in 2.1.245.

From the changelog:

## 2.1.245 - Fixed a crash on startup on Linux distributions that ship glibc 2.44 (for example Arch Linux, CachyOS and Fedora Rawhide)

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, prints 2.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 -W across the three versions I have locally:

| version | .dynsym entries | 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:

 560: 0000000001d10320  145 FUNC GLOBAL DEFAULT 17 calloc@@GLIBC_2.2.5
 700: 0000000001d102d0   79 FUNC GLOBAL DEFAULT 17 malloc@@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

2.1.245 exports none of them, and its .dynsym count is back to the 2.1.241 figure. The ELF type is still EXEC (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 the free(NULL) that __newlocale performs during static init reaches libc's own free, 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:

readelf --dyn-syms -W ~/.local/share/claude/versions/<version> | grep -E ' (malloc|free|calloc|realloc)@'

No output means the build does not interpose.

If you are still on a crashing version

latest now serves 2.1.245. The stable channel is a separate pointer and still reads 2.1.231, so an installation pinned to stable will not pick the fix up on its own.

zupd · 5 days ago

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

  • CachyOS (Arch-based), kernel 7.1.6-1-cachyos, x86_64
  • glibc 2.44+r24+g16be1518495f-1 (2.44 installed 2026-08-10, so it predates 2.1.241, which works)
  • Native install, ~/.local/share/claude/versions/2.1.243, Bun v1.4.0 embedded in both 2.1.241 and 2.1.243
❯ ~/.local/share/claude/versions/2.1.241 --version
2.1.241 (Claude Code)
❯ ~/.local/share/claude/versions/2.1.243 --version
fish: Job 1, '…' terminated by signal SIGSEGV (Address boundary error)

Same backtrace, same addresses as the OP (free at 0x1d10430, fault at free+40, rax=rcx=rdi=rsi=0). readelf --dyn-syms shows malloc@@GLIBC_2.2.5 / free@@GLIBC_2.2.5 exported in 2.1.243 and absent in 2.1.241. LC_ALL=C and LC_ALL=C.UTF-8 crash identically.

1. The exact glibc call site (with glibc debug symbols via debuginfod):

#1  0x00007fbad6a39d0b in __newlocale (...) at newlocale.c:291
291	  free (tmp_buffer);

tmp_buffer is the scratch buffer glibc 2.44's newlocale only 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 plain free(NULL).

2. Direct proof of the interposition, from the core

The call is glibc's internal GOT call (-fno-plt):

0x7fbad6a39d05 <__newlocale+53>:  call   QWORD PTR [rip+0x204015]   # 0x7fbad6c3dd20
(gdb) x/gx 0x7fbad6c3dd20
0x7fbad6c3dd20:	0x0000000001d10430
(gdb) info symbol *(void**)0x7fbad6c3dd20
free in section .text of /home/<user>/.local/share/claude/versions/2.1.243

i.e. libc's own GOT slot for free is bound to the mimalloc free inside the claude binary.

Full chain:

__libc_start_main
  → Bun static init (pthread_once ×2)
    → newlocale(LC_ALL_MASK, "C", NULL)                      [WebKit/WTF]
      → newlocale.c:291  free(tmp_buffer), tmp_buffer==NULL   [glibc 2.44]
        → call *GOT[free] → 0x1d10430 free@@GLIBC_2.2.5       [exported since 2.1.242]
          → free+40: mov rdi,[rax+rcx]  rax=rcx=0             → SIGSEGV SEGV_MAPERR @ 0x0

strace confirms nothing but ld.so library mapping happens before the SIGSEGV — no config, argv, network, or ~/.claude involvement.

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. Any free(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".