[BUG] Linux daemon self-update leaves claude.exe as npm stub when install scripts are blocked
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?
Claude Code's background/daemon self-update path can repeatedly replace the daemon executable with the npm package's 500-byte "native binary not installed" stub when npm blocks package lifecycle scripts.
This is still reproducible with Claude Code 2.1.217 on Linux. It is the same class as the stale, closed anthropics/claude-code#62684, but with fresh Linux evidence and npm 12's allow-scripts policy rather than only macOS ignore-scripts=true.
Observed sequence:
- A running Claude Code background supervisor noticed
/usr/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exechanged and attempted to self-restart. - The self-respawn failed with
ENOEXECbecause that path was an ASCII stub, not the native ELF binary. - The platform-specific optional package was present at
node_modules/@anthropic-ai/claude-code-linux-x64/claude; onlypostinstallhad been blocked, so the native binary was never linked/copied overbin/claude.exe. - The same failure repeated across three consecutive updates/repairs: 2.1.215, 2.1.216, and 2.1.217.
The root cause in the npm logs is that the root/global npm install blocked the package script:
warn install-scripts @anthropic-ai/claude-code@2.1.217 (postinstall: node install.cjs)
warn install-scripts Run `npm install -g --allow-scripts=@anthropic-ai/claude-code` to allow these scripts once, or `npm config set allow-scripts=@anthropic-ai/claude-code --location=user` to allow them for all global installs.
After that install, bin/claude.exe was the stub. The daemon then tried to posix_spawn it and failed before the stub could print its own diagnostic.
The local workaround was:
sudo npm config set allow-scripts=@anthropic-ai/claude-code --location=user
sudo npm install -g @anthropic-ai/claude-code@2.1.217 --include=optional --foreground-scripts
The control reinstall then printed:
npm notice run @anthropic-ai/claude-code@2.1.217 postinstall
npm notice run node install.cjs
and /usr/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe remained a Linux x64 ELF hardlink to the optional package binary. The daemon recovered immediately.
There was also a separate, noisy user-local failure: claude install latest attempted an npm-global install into the user's prefix and failed with EEXIST on an existing native launcher symlink. That wrote a stale .last-update-result.json warning, but the daemon-breaking executable was the root/global /usr stub described above.
What Should Happen?
Claude Code should never leave the executable path watched/spawned by the daemon as the npm stub after a background update attempt.
At minimum, one of these should be true:
- the internal updater invokes npm with
--allow-scripts=@anthropic-ai/claude-codeor otherwise ensuresinstall.cjsruns for this package; - if lifecycle scripts are blocked, the updater aborts before replacing the active executable path;
- if the native package is already present, recovery runs
install.cjsdirectly instead of usingnpm install -g; - the daemon's mtime-triggered self-restart verifies that the target is an actual native executable before spawning it;
- the npm stub has a shebang/executable failure path so subprocess users get the intended remediation text instead of raw
ENOEXEC.
The important invariant is: a failed or policy-blocked update must not turn a previously working Claude Code daemon into a broken install.
Error Messages/Logs
[supervisor] binary at /usr/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe changed (mtime changed) -- self-restarting for upgrade
[supervisor] upgrade self-respawn failed to spawn: ENOEXEC: unknown error, posix_spawn '/usr/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe' -- bg workers may be orphan-reaped ~60s after this process exits unless a client restarts the daemon (run `claude agents`)
Before repair:
$ file /usr/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe
/usr/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe: ASCII text
$ /usr/bin/claude --version
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).
The optional native binary was present:
$ ls -l /usr/lib/node_modules/@anthropic-ai/claude-code/node_modules/@anthropic-ai/claude-code-linux-x64/claude
-rwxr-xr-x 1 root root 268573680 ... claude
After setting root npm allow-scripts and reinstalling the same version:
npm notice run @anthropic-ai/claude-code@2.1.217 postinstall
npm notice run node install.cjs
$ file /usr/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe
/usr/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe: ELF 64-bit LSB executable, x86-64, dynamically linked
$ claude daemon status
pid: <pid>
version: 2.1.217
control.sock: reachable
Steps to Reproduce
One minimal reproduction for the broken package state:
- On Linux x64 with npm 12, use a root/global npm policy that blocks package scripts for
@anthropic-ai/claude-code. - Install or update the npm package under
/usr:
``sh``
sudo npm install -g @anthropic-ai/claude-code@2.1.217
- Observe npm warning that
@anthropic-ai/claude-codepostinstall: node install.cjswas blocked. - Confirm the optional package exists but the public executable path is still the stub:
``sh``
file /usr/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe
ls -l /usr/lib/node_modules/@anthropic-ai/claude-code/node_modules/@anthropic-ai/claude-code-linux-x64/claude
- Run
/usr/bin/claude --versionor let a running daemon self-restart after it notices the mtime change. - The direct CLI prints the stub diagnostic; the daemon self-spawn path fails as raw
ENOEXECand leaves background service control unreachable until manual repair/restart.
This is especially damaging when triggered by Claude Code's own background updater, because it breaks an already-running install.
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Last Working Version
Unknown. Reproduced across 2.1.215, 2.1.216, and 2.1.217 update attempts.
Claude Code Version
2.1.217 (Claude Code)
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
Other
Additional Information
Related issues:
anthropics/claude-code#62684reported the same self-update/stub loop on macOS withignore-scripts=true, but it was closed as stale.anthropics/claude-code#61552covers the same stub surfacing asENOEXECbecause the stub has no shebang, but not the daemon self-update loop.anthropics/claude-code#77384covers a Linux daemon self-respawn failure during npm postinstall too, but withEACCESfrom a write/chmod race; this report is theENOEXECvariant where lifecycle policy preventspostinstallfrom replacing the stub at all.anthropics/claude-code#72193mentions npmallow-scriptsas a separate install issue, but its main report is a Windows silent-exit problem.
This report is specifically about preserving the daemon executable invariant during internal update/recovery: if npm policy blocks lifecycle scripts, Claude Code should not replace a working watched executable with the stub and then immediately try to spawn it.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗