[BUG] Auto-update silently ships a non-functional install on stock npm 12
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?
npm 12 (released 2026-07-08) blocks dependency lifecycle scripts by default unless covered by the root package's allowScripts policy. When Claude Code self-updates via the npm install path, npm blocks its postinstall: node install.cjs, exits 0, and the update completes — but the native binary is never moved into place.
The platform optional dependency downloads correctly (@anthropic-ai/claude-code-linux-x64/claude, ~324 MB, mode 755, intact). Because install.cjs never runs, bin/claude.exe — the path node_modules/.bin/claude resolves to — keeps the shipped fallback stub. The result is an installation that reports success and does not run.
This requires no user configuration. No flags, ignore-scripts=false, user and global npmrc both empty; verbose argv in the npm debug log confirms a bare install invocation. It affects every npm-path user as they reach npm 12.
The stub also has no shebang. Callers going through a shell see its (helpful) error text; callers using execve/posix_spawn get ENOEXEC instead, losing the diagnostic entirely.
What Should Happen?
An update that cannot complete should not report success, and should not replace a working install.
Specifically:
- Verify before declaring success — confirm post-install that the binary is real (size, executability, or a
--versionsmoke test). npm's exit code is not sufficient here. - Pre-flight the policy — detect blocked scripts before updating and abort, leaving the existing working install intact.
- Surface the fix — tell the user to run
npm install-scripts approve @anthropic-ai/claude-code. Writing that entry automatically is arguably defensible (the updater is already-trusted code running as the user, not an untrusted package escalating inside npm's sandbox), but it loosens a security control the user may have adopted deliberately, so it should be announced rather than silent. - Don't misroute on suppression — have
install.cjswrite the real binary to a distinct path and make the stub a launcher, so a blocked postinstall fails visibly instead of silently substituting a non-functional file. - Add a shebang to the stub so its error message survives non-shell invocation.
Error Messages/Logs
npm warning during install (npm 12.0.2):
npm warn install-scripts @anthropic-ai/claude-code@2.1.233 (postinstall: node install.cjs)
`npm install-scripts ls`:
1 package has install scripts blocked because they are not covered by allowScripts:
@anthropic-ai/claude-code@2.1.233 (postinstall: node install.cjs)
Stub contents (first bytes — note: no shebang):
echo "Error: claude native binary not installed." >&2
Via shell:
Error: claude native binary not installed.
Either postinstall did not run (--ignore-scripts, some pnpm configs)
or the platform-native optional dependency was not downloaded
(--omit=optional).
Via `posix_spawn` (agent session spawning):
Error: Failed to spawn session <id>: ENOEXEC: unknown error, posix_spawn
'/home/<user>/.claude/local/node_modules/@anthropic-ai/claude-code/bin/claude.exe'
npm debug log, confirming no flags were passed:
8 verbose argv "install" "@anthropic-ai/claude-code@2.1.233"
**Environment:** Claude Code 2.1.233 · npm 12.0.2 · Arch Linux x86_64, unprivileged LXC container · npm local install under `~/.claude/local`
Steps to Reproduce
- Use npm >= 12.0.0 with the package not previously approved (fresh environment, or one where
allowScriptshas no entry for it). - Run
npm install @anthropic-ai/claude-code. - Observe npm exits 0, with only a warning about blocked install scripts.
file node_modules/@anthropic-ai/claude-code/bin/claude.exe→ reports ASCII text, not an ELF binary.npm install-scripts ls→ shows the package as blocked.- Run the resulting
claude— via a shell it prints "native binary not installed"; viaexecve/posix_spawnit fails withENOEXEC.
In the observed case this occurred through background auto-update rather than a manual install, so steps 1–3 happened unattended.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.233
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
iTerm2
Additional Information
Relationship to existing issues. #62684 reports the same stub-instead-of-binary outcome on self-update, but triggered by a user-set ignore-scripts=true. Filing separately because the trigger has moved from opt-in to default: that report reads as affecting a security-conscious minority, whereas this fires on stock configuration for everyone reaching npm 12. Its suggested fix (write the binary to a distinct path; reconsider self-update tearing the binary out from under live processes) applies here too. Also related: #50203 (Bun blocks the same postinstall) and npm/cli#9738 (the approve path has bugs of its own — ENOMATCH, "no trusted identity for policy key").
Why this is hard to diagnose. The failure is deferred — the broken update landed ~04:00, nothing failed until ~15:40 when a new session needed the binary. The symptom points away from the cause: .exe plus ENOEXEC on Linux reads as a wrong-platform binary. Every config surface reads clean. Debug logs show three identical install attempts ten minutes apart, so something retried, but an identical npm install against a policy block only reproduces the block.
Naming varies by npm version. 12.0.2 emits install-scripts (npm install-scripts ls|approve); earlier versions emit allow-scripts (npm approve-scripts --allow-scripts-pending). Docs should cover both.
Structural note. The native installer already avoids this class of failure — versions stage under ~/.local/share/claude/versions/ and a symlink flips, so a bad download never becomes live. The npm path has no equivalent atomic swap or rollback.
Workaround.
npm install-scripts approve @anthropic-ai/claude-code
node node_modules/@anthropic-ai/claude-code/install.cjs
Attribution. Drafted with Claude Opus 5 (claude.ai chat interface, extended thinking enabled; web search used to confirm npm 12 behavior and locate related issues). All command output was run and verified by the human reporter; the model had no access to the affected host. Environment details generalized to remove identifying information. Diagnosis took roughly six exchanges, with several wrong hypotheses first (wrong-platform binary, disk exhaustion, container OOM, ZFS snapshot rollback) before the npm debug log revealed the cause — which supports the diagnosability point above. Conclusions are the model's synthesis of human-verified evidence and warrant checking rather than assumption.
3 Comments
Confirmed / reproduced on 2.1.234 with npm 12.0.2 (Linux):
npm install @anthropic-ai/claude-codeexits 0 with only aninstall-scriptswarning that the postinstall was blocked; the platform package is downloaded, butbin/claude.exeremains the ~500-byte text stub (filereports ASCII text), and runningclaudeprints "claude native binary not installed" and exits 1. The same install with npm 10.9 on the same machine produces a working binary.npm install-scripts approve @anthropic-ai/claude-codefollowed by re-running the postinstall works around it, as you noted.🤖 Generated with Claude Code
---
_Generated by Claude Code_
Hitting this on Linux, and I think there is a second, independent way to reach the
same broken state that is worth separating from the
allowScriptscause in thereport — because the diagnostic you get points somewhere different.
Variant: the JS package is published ahead of the platform binary.
@anthropic-ai/claude-code@2.1.237pins its native optional dependencies to theexact version:
As of 2026-08-20 01:05 UTC, that version does not exist:
npm silently skips an optional dependency it cannot resolve, so the install
completes with
node_modules/entirely empty:This matters for diagnosis. In the original report the optional dependency
downloads correctly and only the postinstall link is missing, so the binary is
present and repairable. Here there is nothing to link, and running the
postinstall by hand says so:
Neither of those two suggestions applies — no flags were passed, and nothing
failed to download. The version simply is not published. The message sends you
looking at your own npm configuration, which is where I spent the first twenty
minutes.
The end state is byte-identical to the report:
bin/claude.exeis the 500-byteshebang-less stub, and
node_modules/.bin/clauderesolves to it.The auto-updater actively fights the workaround. Pinning to the last version
whose native package exists is undone within seconds. From
~/.npm/_logs,unedited:
So on a machine with a session running, there is currently no self-service fix
at all: 2.1.237 cannot install its binary, and 2.1.236 does not survive long
enough to be used. #88091 describes the updater racing itself; this is the
updater racing the operator's remediation, with the same outcome.
Environment
It does not block lifecycle scripts outright but does warn:
npm warn allow-scripts 1 package has install scripts not yet covered by allowScriptsso both causes are live on the same box.
~/.local/share/mise/installs/node/26.5.0/lib/node_modules/@anthropic-ai/claude-codemakes the contrast easy to see:
``
``node 25.8.0 claude-code 2.1.222 node_modules/@anthropic-ai/claude-code-linux-x64 present
node 26.5.0 claude-code 2.1.237 node_modules/@anthropic-ai/ empty
Knock-on effect worth noting for the "fails loudly" argument. The blast
radius is wider than the CLI itself. Anything that shells out to
claudeinherits the failure, and the stub's text mentions
--ignore-scriptsand--omit=optional— so an unrelated tool suddenly fails with what reads like apackaging complaint about that tool. On this machine every
sshinvocationfrom an automation shell started returning the postinstall error mid-session,
and the obvious first hypothesis was a network or SSH-agent problem. The install
had been working ten minutes earlier and nothing about it had been touched.
Suggested addition to the fixes already proposed in the report: whatever
publishes the release should not tag the JS package
latestuntil everyplatform binary of that exact version is published, and the updater should
refuse to move to a version whose platform binary it cannot resolve, rather than
installing it and leaving the machine worse than before.
For your information: the label
reproducedon this issue does not prevent it from becoming stale and eventually auto-closed. See https://github.com/anthropics/claude-code/issues/87647 for details, and consider upvoting that for visibility.