[BUG] Native installer (2.1.243) segfaults in newlocale/free during startup on glibc 2.44 (CachyOS)

Status Open
Reported on v2.1.205
Maintainer reply None cached
Activity 8 comments · opened Aug 25, 2026

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-desktop installed from AUR (package claude-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_vnni in /proc/cpuinfo.
  • Not a broken/missing locale: LANG/LC_ALL point to en_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

  1. curl -fsSL https://claude.ai/install.sh | bash
  2. 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).

  1. Running the resulting binary directly at ~/.local/share/claude/versions/2.1.243 also

segfaults immediately, with no arguments needed.

  1. claude uninstall on 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_

View original on GitHub ↗

8 Comments

jgyy · 6 days ago

i encountered same issue for my cachyos, have to uninstall and reinstall 2.1.241 and disable update

zytoc · 6 days ago

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.

Obarun · 6 days ago

Same crash on a different distro, and I think I have the root cause: 2.1.243 exports malloc/free/calloc/realloc as global dynamic symbols, which interposes over glibc's own allocator. 2.1.241 does not export them.

Confirmation on a second system

  • OS: Obarun (Arch-based, 66 — no systemd)
  • Kernel: 6.12.10-arch1-1 x86_64
  • glibc: 2.44
  • Install method: native installer, ~/.local/share/claude/versions/
  • CPU: AVX/AVX2 present
  • No claude-desktop AUR package installed <!-- ← vérifie et corrige si besoin -->

Identical signature to the original report:

claude[14453]: segfault at 0 ip 0000000001d10458 sp 00007ffff46664f0 error 4
  in 2.1.243[1b0f458,1953000+3b33000]

Note that the faulting address 0x1d10458 is identical to the one in the original report, on a
different 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:

$ for v in ~/.local/share/claude/versions/*; do
    printf '%s: ' "$(basename "$v")"; "$v" --version 2>&1 | tail -1
  done
2.1.238: 2.1.238 (Claude Code)
2.1.239: 2.1.239 (Claude Code)
2.1.241: 2.1.241 (Claude Code)
2.1.243:            <- no output, SIGSEGV before printing anything

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

$ for v in 2.1.241 2.1.243; do
    echo "== $v"
    nm -D --defined-only ~/.local/share/claude/versions/$v | grep -wE 'malloc|free|calloc|realloc'
  done
== 2.1.241
== 2.1.243
0000000001d10320 T calloc@@GLIBC_2.2.5
0000000001d10430 T free@@GLIBC_2.2.5
0000000001d102d0 T malloc@@GLIBC_2.2.5
0000000001d103c0 T realloc@@GLIBC_2.2.5

2.1.241 exports nothing. 2.1.243 exports all four as global (T) symbols, versioned GLIBC_2.2.5.

The addresses line up exactly with the crash:

  • nm: free at 0x1d10430
  • gdb: rip 0x1d10458 <free+40> → 40 bytes into that same free
  • the address falls inside the executable's own mapping (0x19530000x5486000), not in libc.so.6

Since 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() allocates
with glibc's allocator and frees through the PLT, which lands in the bundled free(), which receives
a pointer it never allocated — during pre-main initialization, before it is even fully set up.

Backtrace (gdb, same as the coredump in the original report)

Program received signal SIGSEGV, Segmentation fault.
0x0000000001d10458 in free ()
#0  0x0000000001d10458 in free ()
#1  0x00007ffff7d9630a in newlocale () from /usr/lib/libc.so.6
#2  0x0000000001abd282 in ?? ()
#3  0x00007ffff7dfe7fc in ?? () from /usr/lib/libc.so.6
#4  0x00007ffff7dfe879 in pthread_once () from /usr/lib/libc.so.6
...
#16 0x00007ffff7d88892 in __libc_start_main () from /usr/lib/libc.so.6
#17 0x0000000001953a2e in ?? ()

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

  • strace shows the process dies with no syscall of its own after set_robust_list — no

openat, no socket, no large JIT mmap. It never reaches main; --version prints nothing.

  • env -i HOME=$(mktemp -d) .../2.1.243 --version → still SIGSEGV. Independent of environment,

config, ~/.claude, and session state.

  • ldd output is byte-for-byte identical between 2.1.241 and 2.1.243 (same 7 objects). No new

dependency was introduced.

  • strace syscall sequences for 2.1.241 and 2.1.243 are identical up to the crash point.
  • Not AVX-related: avx/avx2 present in /proc/cpuinfo, and 2.1.241 runs fine on this same CPU.
  • Not download corruption: a freshly downloaded binary from install.sh faults at the same address

as 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, …) as local, or building the bundled allocator without its
libc-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:

ln -sf ~/.local/share/claude/versions/2.1.241 ~/.local/bin/claude
hash -r

plus "autoUpdaterStatus": "disabled" in ~/.claude/settings.json, otherwise the auto-updater
reinstalls the broken build. There is no user-side fix for the crash itself — LD_PRELOAD cannot
override an executable's own symbol definitions.

dispather · 6 days ago

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: false in ~/.claude.json does not stop the updater. I had it set to false and the updater still ran and flipped the symlink:

// ~/.claude/.last-update-result.json — written while autoUpdates was already false
{"timestamp":"2026-08-25T00:50:59.838Z","path":"native","outcome":"success","status":"success",
 "version_from":"2.1.241","version_to":"2.1.243","error_code":null}

There is also a "autoUpdatesProtectedForNative": true key 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=1 is 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:

export DISABLE_AUTOUPDATER=1     # put it somewhere persistent, see note below
ln -sfn ~/.local/share/claude/versions/2.1.241 ~/.local/bin/claude

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 its EnvironmentFile.

3. Verifying the pin. Don't verify from inside an existing Claude Code session — it inherits CLAUDE_CODE_EXECPATH pointing 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:

readlink -f ~/.local/bin/claude          # still .../versions/2.1.241
stat -c '%y' ~/.claude/.last-update-result.json   # mtime not advanced

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 autoUpdatesChannel was "latest". At the same moment stable was 2.1.231, which is unaffected. If you are not deliberately dogfooding, stable would have kept you out of this entirely.

steevee · 6 days ago

Fourth independent reproduction (Arch Linux, kernel 7.1.9-arch1-2, glibc 2.44+r24+g16be1518495f, x86-64), same faulting address 0x1d10458, 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 broken

The script takes a version argument and validates it:

TARGET="$1"  # Optional target parameter

if [[ -n "$TARGET" ]] && [[ ! "$TARGET" =~ ^(stable|latest|[0-9]+\.[0-9]+\.[0-9]+(-[^[:space:]]+)?)$ ]]; then
    echo "Usage: $0 [stable|latest|VERSION]" >&2
    exit 1
fi

But TARGET is not used to select the binary that gets downloaded and run. That is always latest:

# Always download latest version (which has the most up-to-date installer)
version=$(download_file "$DOWNLOAD_BASE_URL/latest")
...
binary_path="$DOWNLOAD_DIR/claude-$version-$platform"

TARGET is only ever passed through to the downloaded binary's own install subcommand, on the last line before cleanup:

echo "Setting up Claude Code..."
install_code=0
"$binary_path" install ${TARGET:+"$TARGET"} || install_code=$?

So the pinning path is bootstrapped by a latest binary. While latest is 2.1.243, that bootstrap binary hits this exact segfault before it can parse its own argument, and the requested version is never reached:

$ curl -fsSL https://claude.ai/install.sh | bash -s 2.1.241
Setting up Claude Code...
bash: line 226: 24287 Segmentation fault (core dumped) "$binary_path" install ${TARGET:+"$TARGET"}
Installation was killed before it could finish (exit code 139).

$ curl -fsSL https://claude.ai/install.sh | LC_ALL=C bash -s 2.1.241
(identical segfault; LC_ALL=C makes no difference, consistent with @redboltz in #89369)

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.sh is the documented path and it is broken by the same defect it would be used to escape. Downloading a known-good binary directly from downloads.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 latest re-breaks the version-pinning escape hatch the same way. Selecting the bootstrap binary by TARGET when one is supplied, rather than always by latest, would make the flag do what its usage string promises.

Local reproduction detail

claude --version is enough; no config or network access needed. 2.1.241 on the same machine is fine.

$ ~/.local/share/claude/versions/2.1.243 --version
fish: Job 1, '...' terminated by signal SIGSEGV (Address boundary error)

$ ~/.local/share/claude/versions/2.1.241 --version
2.1.241 (Claude Code)

<details>
<summary>Core dump (<code>coredumpctl info</code>)</summary>

        Signal: 11 (SEGV) si_code: SEGV_MAPERR

                Stack trace of thread 23544:
                #0  0x0000000001d10458 free (2.1.243 + 0x1b10458)
                #1  0x00007f8d2d63530a __newlocale (libc.so.6 + 0x3530a)
                #2  0x0000000001abd282 n/a (2.1.243 + 0x18bd282)
                #3  0x00007f8d2d69d7fc n/a (libc.so.6 + 0x9d7fc)
                #4  0x00007f8d2d69d879 pthread_once (libc.so.6 + 0x9d879)
                #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  0x00007f8d2d69d7fc n/a (libc.so.6 + 0x9d7fc)
                #10 0x00007f8d2d69d879 pthread_once (libc.so.6 + 0x9d879)
                #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 0x00007f8d2d627892 __libc_start_main (libc.so.6 + 0x27892)
                #17 0x0000000001953a2e n/a (2.1.243 + 0x1753a2e)
                ELF object binary architecture: AMD x86-64

</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 doctor is not usable for diagnosis, since the affected binary segfaults before it runs.

My only recovery was manually repointing the ~/.local/bin/claude symlink at the cached 2.1.241.

dsolis-deloryx · 6 days ago

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

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 Threadripper 9960X 24-Cores   (avx2 present)
Install:           native installer, ~/.local/share/claude/versions/

Same binaries as everyone else

2.1.241  342636848 bytes  sha256 0771bd866cff82b76581fc0499f6529e1a36845078f144f8c81dccb3bc7037b8  ok
2.1.242  377568472 bytes  sha256 528ef039aa7d64d7b3fbc06925132755a516b4dcaad784cf0b51fe03167360d4  SIGSEGV
2.1.243  377568472 bytes  sha256 4b0dafeedd0b469c41988e200036fd773e7553ba960349c9f02a82c6d1f2ba27  SIGSEGV

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. coredumpctl stack is identical to the one in the report (free at 0x1d10458 in the executable, called from __newlocale, under pthread_once, SEGV_MAPERR). Still crashes with LC_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:

$ for v in 2.1.241 2.1.242 2.1.243; do printf '%s  ' $v; strings -n 5 $v | grep -m1 -oE '1\.4\.0\+[a-f0-9]{9}'; done
2.1.241  1.4.0+fdb5e06cc
2.1.242  1.4.0+19bc277a0
2.1.243  1.4.0+19bc277a0

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 .dynsym diff already posted: fdb5e06cc does not export the allocator, 19bc277a0 does. The Claude Code side changed too (GIT_SHA c87e274 -> 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:

$ readelf -W --dyn-syms 2.1.243 | grep -E ' (__libc_free|__libc_malloc)@'
  1078: 0000000001d102d0    79 FUNC GLOBAL DEFAULT   17 __libc_malloc@@GLIBC_2.2.5
  1156: 0000000001d10430   126 FUNC GLOBAL DEFAULT   17 __libc_free@@GLIBC_2.2.5

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:

$ ~/.local/share/claude/versions/2.1.241 install 2.1.243
...
  Location: ~/.local/bin/claude
$ readlink ~/.local/bin/claude
/home/sd/.local/share/claude/versions/2.1.243
$ claude --version
zsh: segmentation fault (core dumped)  claude --version
$ claude upgrade          # from the pinned 2.1.241
Current version: 2.1.241
Checking for updates to latest version...
Successfully updated from 2.1.241 to version 2.1.243
$ claude --version
zsh: segmentation fault (core dumped)  claude --version

~/.claude/.last-update-result.json records 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 was DISABLE_AUTOUPDATER=1; setting it in the env block of ~/.claude/settings.json works and survives shell changes:

"env": { "DISABLE_AUTOUPDATER": "1" }

("autoUpdates": false at the top level is rejected by the settings schema on 2.1.241: Unrecognized field: autoUpdates.)

Suggestion for the fix release: have install / upgrade run <new binary> --version before moving the symlink, and leave it alone if that exits non-zero. It would have caught this on the first machine.

dispather · 6 days ago

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. ~/.claude2 and friends), a session started under a profile that lacks the key will run the updater and flip ~/.local/bin/claude for every profile, including the one you just pinned.

What I observed

I had already done the full pin: DISABLE_AUTOUPDATER=1 in ~/.claude/settings.json env, 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.

$ ls -la --time-style=full-iso ~/.local/bin/claude
... 2026-08-25 10:21:18.489296011 +0900 claude -> ~/.local/share/claude/versions/2.1.243

$ cat ~/.claude2/.last-update-result.json
{"timestamp":"2026-08-25T01:21:18.489Z","path":"native","outcome":"success","status":"success",
 "version_from":"2.1.241","version_to":"2.1.243","error_code":null}

$ cat ~/.claude/.last-update-result.json     # my first break, 30 min earlier — untouched
{"timestamp":"2026-08-25T00:50:59.838Z","path":"native","outcome":"success","status":"success",
 "version_from":"2.1.241","version_to":"2.1.243","error_code":null}

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:

$ jq -r '.env.DISABLE_AUTOUPDATER // "absent"' ~/.claude/settings.json
1
$ jq -r '.env.DISABLE_AUTOUPDATER // "absent"' ~/.claude2/settings.json
absent

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-environment did not list it).
  • A shell rc only reaches shells started after it is written. Terminals already open when you pin are still clean of the variable.

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:

  1. Make the updater's pre-flight check consider whether the symlink it is about to move is shared, or at minimum honor a machine-scoped suppression, not only a profile-scoped one. Today there is no documented place to disable the updater for the host.
  2. @dsolis-deloryx's proposal — run <new binary> --version before 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:

for d in ~/.claude ~/.claude[0-9]*; do
  printf '%s: %s\n' "$d" "$(jq -r '.env.DISABLE_AUTOUPDATER // "absent"' "$d/settings.json" 2>/dev/null)"
done

(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.)

James-P-Bennett · 6 days ago

Confirming on CachyOS (kernel 7.2.0-1-cachyos, glibc 2.44, x86_64) with the same signature — free at 2.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-1 on 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 | free in 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:

Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000000001d10458 in ?? ()
rdi            0x0                 0
$1 = (void *) 0x0          # si_addr

free(NULL) is a documented no-op in glibc, so __newlocale calling it unconditionally is correct — the interposed free just doesn't guard against NULL.