[Bug] npm-global auto-updater + macOS AppleSystemPolicy guts package directory, breaks CLI

Resolved 💬 3 comments Opened Apr 16, 2026 by TheMizeGuy Closed Apr 19, 2026

Summary

The CLI's built-in auto-updater runs npm install -g @anthropic-ai/claude-code to update the npm-global installation. On macOS, AppleSystemPolicy (Gatekeeper) blocks the newly-written cli.js from executing, causing the install to fail partway through. Because npm deletes old package files before writing new ones, the failed install leaves the package directory gutted — only vendor/ripgrep/ survives. The CLI becomes completely unusable (command not found) until manually reinstalled.

Steps to Reproduce

  1. Install Claude Code globally via npm: npm install -g @anthropic-ai/claude-code
  2. Start one or more Claude Code sessions
  3. Wait for the auto-updater to detect a newer version and trigger npm install -g
  4. If macOS AppleSystemPolicy blocks the new cli.js, the package directory is gutted

Expected Behavior

  • The auto-updater should not leave the package directory in a broken state on install failure
  • At minimum, the old installation should be preserved if the new one fails (atomic swap or backup-before-delete)

Actual Behavior

After the failed update, /opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/ contains only:

vendor/
  ripgrep/    (empty)

No cli.js, no package.json, no bin symlink at /opt/homebrew/bin/claude. New terminal sessions get command not found: claude. Running sessions continue (code in memory) but cannot spawn children.

Evidence (macOS unified log)

Full timeline from log show:

| Time | Event |
|------|-------|
| 21:01–21:18 | Repeated npm view @anthropic-ai/claude-code@latest version checks (~every 1-7 min) |
| 21:08:08 | First npm install @anthropic-ai/claude-code |
| 21:15:24 | Second install attempt |
| 21:21:53 | Third install — npm deletes old files, writes new ones |
| 21:21:55 | kernel: (AppleSystemPolicy) ASP: Security policy would not allow process: 3730, /opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js (3 blocks) |
| 21:21:55 | Immediate retry within 2 seconds |
| 21:23:03 | Another retry |
| 21:23:03 | kernel: (AppleSystemPolicy) ASP: Unable to retrieve vnode for script: cli.js — file is gone (2,671 errors as running sessions try to access the deleted binary) |

The key transition: at 21:21:55 ASP blocks the new file ("would not allow"), and by 21:23:03 the file doesn't exist at all ("unable to retrieve vnode") — the retry deleted whatever remained.

Log commands to reproduce diagnostics

# Version check + install attempts
/usr/bin/log show \
  --predicate 'eventMessage CONTAINS "claude-code" OR eventMessage CONTAINS "npm install"' \
  --start "2026-04-15 21:00:00" --end "2026-04-15 21:25:00" --style compact

# ASP blocks
/usr/bin/log show \
  --predicate 'eventMessage CONTAINS "Security policy" AND eventMessage CONTAINS "cli.js"' \
  --start "2026-04-15 21:00:00" --end "2026-04-15 21:24:00" --style compact

Root Cause Analysis

Three interacting problems:

1. npm's non-atomic global install

npm install -g deletes the old package directory contents before writing new files. If the new install fails for any reason, the old installation is destroyed with no rollback.

2. macOS AppleSystemPolicy blocks the new cli.js

The newly-downloaded cli.js is blocked by Gatekeeper/ASP, likely due to quarantine attributes on the npm-fetched tarball. This causes the install's postinstall or verification step to fail.

3. Aggressive retry without backoff

The auto-updater retries within seconds of failure. Each retry re-triggers npm's delete-then-install cycle, compounding the damage. Three install attempts in 90 seconds (21:21:53, 21:21:55, 21:23:03).

Recovery

npm install -g @anthropic-ai/claude-code

Proposed Fixes

  1. Backup before update: Copy the existing installation to a temp location before running npm install -g. Restore on failure.
  2. Verify before deleting: Check that the new binary is executable (passes ASP) before removing the old one.
  3. Backoff on failure: Don't retry immediately. Exponential backoff (minutes, not seconds).
  4. Clear quarantine: Run xattr -d com.apple.quarantine on the new cli.js after npm writes it, before attempting execution.
  5. Respect DISABLE_AUTOUPDATER: Per #36250, this env var doesn't prevent the version check. It should also prevent the install to avoid this scenario entirely.

Workaround

# Disable the auto-updater entirely
# In ~/.claude/settings.json:
{
  "env": {
    "DISABLE_AUTOUPDATER": "1"
  }
}

Note: per #36250, this only prevents the install step, not the npm view version checks.

Relationship to Other Issues

| Issue | Relationship |
|-------|-------------|
| #47253 | Same category (auto-updater destroys running install) but different install method (native installer vs npm-global) and different failure mechanism (binary unlinked vs ASP-blocked install guts package dir) |
| #36250 | DISABLE_AUTOUPDATER doesn't suppress npm view — the version check that triggers the install in this bug |
| #41602 | Auto-updater fails to activate downloaded version — same updater, different failure mode |
| #43719 | Auto-update data loss (Cowork) — same class of "updater destroys state" bugs |

Environment

  • macOS 26.2 (Darwin 25.2.0), Apple Silicon (arm64)
  • Claude Code 2.1.110 (post-recovery), installed via npm install -g
  • Node v25.8.2, npm 11.11.1
  • npm prefix: /opt/homebrew

View original on GitHub ↗

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