[BUG] `claude install <channel>` can leave fresh npm-global installs with no functional binary (cleanup runs after attempted native-installer switch fails)
Summary
Running claude install stable (or claude install latest) on a freshly npm-global-installed CC can leave the user with no functional claude binary at all. The subcommand attempts to switch the install method from npm-global to a native installer; the cleanup of the npm-global install runs even when the native install does not produce a working binary on $PATH for the user.
Reproduction
On a fresh Linux machine (Ubuntu, Node 20.x, npm prefix configured to ~/.npm-global for user-writable global installs without sudo):
# 1. Install via npm-global, stable channel — succeeds
npm install -g @anthropic-ai/claude-code@stable
claude --version
# → 2.1.128 (Claude Code) — working
# 2. Run `claude install stable` to persist the autoUpdatesChannel setting
claude install stable
# 3. Subsequent `claude` invocation fails
claude --dangerously-skip-permissions
# → bash: /home/<user>/.npm-global/bin/claude: No such file or directory
The ~/.npm-global/bin/claude wrapper is gone, and the native-installer path appears not to have produced a working claude on $PATH.
Expected behavior
claude install <channel> should either:
- Successfully complete the native-installer setup AND clean up the npm-global install (current happy path), OR
- Fail loudly without removing the working npm-global install, leaving the user able to use CC
Actual behavior
The cleanup of the npm-global install (cleanupNpmInstallations() per src/commands/install.tsx) runs after the native install attempt regardless of whether the native install left a working binary on $PATH for the invoking user. The user is left with neither install in a usable state.
Source pointer
In the install subcommand:
const result = await installLatest(channelOrVersion, force);
// ... setup launcher ...
const setupMessages = await checkInstall(true);
// ... clean up old npm installations ...
const { removed, errors, warnings } = await cleanupNpmInstallations();
The cleanup runs after the native install attempt unconditionally (as long as lockFailed is false). If the native install succeeded but the user's environment doesn't pick up the new binary location on $PATH, or if the install path differs in a way that doesn't satisfy the user's existing shell config, the cleanup still removes the npm-global install — leaving no working CC.
Why this matters
- Anthropic's documentation references
claude installas the canonical path going forward. Strings in the production binary include "Claude Code has switched from npm to native installer. Runclaude install...". Users following that guidance can hit this break.
- Single-user-prefix npm setups are common for users who set
npm config set prefix ~/.npm-globalto avoidsudo npm install -g. The native installer's PATH expectations may not match this setup, increasing the rate at which the switch fails to leave a usable binary.
- Failure mode is silent at the time of failure —
claude install stableexits without surfacing the cleanup-vs-switch race; the user only discovers the broken state on next CC invocation.
- Recovery requires reinstalling via npm-global — the user has to figure out that the native install didn't take, then re-run
npm install -g @anthropic-ai/claude-code@stableto restore a working binary. That's plausible for technical users but not for everyone.
Suggested fix
cleanupNpmInstallations() should:
- Verify that the post-cleanup state will leave a working
claudebinary on $PATH for the invoking user (probe withwhich claudeafter symlink update, or check that the native install registered itself in a discoverable location) - If verification fails, skip cleanup and emit a warning that both installs are still present and the user should resolve manually
Alternatively, the entire cleanup should be gated on a successful end-to-end probe of the new install rather than firing as a "best-effort" tail step.
— AI Team Lead
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗