[BUG] ignore-scripts=true + auto-update interaction crashes running Claude Code session; native binary repeatedly replaced with error stub on every self-update
Environment
- macOS arm64
@anthropic-ai/claude-codeinstalled via Homebrew (symlink at/opt/homebrew/bin/claude→ npm package at/opt/homebrew/lib/node_modules/...)~/.npmrchasignore-scripts=trueset globally as a baseline security posture against postinstall-based supply-chain attacks- Auto-update enabled (the Claude Code default)
What happens
In a single ~6-hour working session today, Claude Code's background self-update fired three separate times as new 2.1.x patch versions shipped. Each self-update reran the npm install under my ignore-scripts=true posture, the postinstall (install.cjs) was suppressed, and claude.exe was left as the 500-byte error-stub instead of being replaced with the real native binary fetched from the platform-specific optional dependency.
Symptoms in order:
- The running interactive Claude Code session crashed and dumped me back to my terminal home screen — not just a command exiting nonzero, the running process itself was killed mid-conversation.
- Attempting to restart with the
claudecommand printed the familiarError: claude native binary not installed. Either postinstall did not run (--ignore-scripts...) ...message. - Running the Claude Code VS Code extension restored the binary after a few seconds (it ran something that the UI described as "doing the binaries" — almost certainly
install.cjsexecuting). - Restarting the terminal and rerunning
claudeworked again.
Each occurrence also killed an in-flight background workflow my session was orchestrating — three of the four times, the background bash subprocess running claude --print ... died along with the parent and the workflow lost state. The one survival case happened during a brief drafter-to-reviewer handoff window where the auto-update timing didn't intersect a live subprocess.
Why I think this is distinct from existing issues
- #50609 ("claude native binary not installed") names
--ignore-scriptsas a known root cause but is filed against the initial install path on Windows. It doesn't address the auto-update interaction or the in-session-crash symptom. - #51266 ("vendor/ripgrep not created by install.cjs when ignore-scripts=true") covers a related ignore-scripts gap but is filed against the Grep tool's vendored ripgrep, not the claude binary itself, and was closed as duplicate.
- #57178 ("Auto-update creates broken claude.exe symlink on macOS") covers auto-update breaking the install on macOS, but the failure mode there is a platform-detection bug pointing the symlink at a Windows binary that doesn't exist. My case is different:
claude.exedoes exist at the expected path, it's just the 500-byte error stub because the postinstall that would have replaced it was suppressed.
None of these capture "user with ignore-scripts=true loses their running session to a background self-update."
Workaround I'm using (works, but is user-side)
A PATH-shim wrapper that detects the stub by file size and runs install.cjs before exec'ing the real binary. Placed in ~/.local/bin/claude ahead of the Homebrew bin in PATH so both bash and zsh subshells route through it.
#!/bin/bash
# auto-healing wrapper around @anthropic-ai/claude-code
set -e
pkg_dir="/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code"
bin="${pkg_dir}/bin/claude.exe"
# install.cjs uses <4096 bytes as the stub-vs-real-binary threshold
if [[ ! -f "$bin" ]] || (( $(stat -f %z "$bin" 2>/dev/null || echo 0) < 4096 )); then
echo "[claude] native binary missing — running postinstall..." >&2
node "${pkg_dir}/install.cjs" >&2
fi
exec "$bin" "$@"
Adjust pkg_dir for non-Homebrew installs. Linux uses stat -c %s instead of stat -f %z. Note that a claude function in ~/.zshrc doesn't cover this case — bash subshells spawned by automation never source ~/.zshrc, so a PATH-shim is what reaches both shells.
Suggested upstream fixes
- Have
install.cjswrite the real binary to a different path than the stub, then write a tiny launcher script as the stub-replacement that always exec's the real binary by absolute path. The current architecture silently misroutes when the postinstall is suppressed; making the failure mode "the launcher script is missing" instead would be louder and more obviously broken. - Consider whether background self-update should suspend itself while a parent Claude Code process holds a child
claudeinvocation. As designed, the self-update tears the running binary out from under live child processes, which is the source of the in-session crashes — even users withoutignore-scripts=truemay be vulnerable to a similar race if the npm install momentarily writes the new binary in a non-atomic way. - Ship a Homebrew formula (or other non-npm install path) so users who want both a security posture of
ignore-scripts=trueand a working claude don't have to maintain a user-side wrapper.
Happy to provide repro logs or test patches.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗