[BUG] Auto-update silently ships a non-functional install on stock npm 12

Status Open
Reported on v2.1.233
Maintainer reply None cached
Activity 2 comments · opened Aug 15, 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?

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:

  1. Verify before declaring success — confirm post-install that the binary is real (size, executability, or a --version smoke test). npm's exit code is not sufficient here.
  2. Pre-flight the policy — detect blocked scripts before updating and abort, leaving the existing working install intact.
  3. 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.
  4. Don't misroute on suppression — have install.cjs write 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.
  5. 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

  1. Use npm >= 12.0.0 with the package not previously approved (fresh environment, or one where allowScripts has no entry for it).
  2. Run npm install @anthropic-ai/claude-code.
  3. Observe npm exits 0, with only a warning about blocked install scripts.
  4. file node_modules/@anthropic-ai/claude-code/bin/claude.exe → reports ASCII text, not an ELF binary.
  5. npm install-scripts ls → shows the package as blocked.
  6. Run the resulting claude — via a shell it prints "native binary not installed"; via execve/posix_spawn it fails with ENOEXEC.

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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗