feat: patch ALL installed cli.js locations for multi-install support

- Add find_all_cli_js() — discovers all cli.js paths across npm global dirs,
  which claude symlink resolution, and NVM per-user installs
- Update cmd_update() to iterate all found paths and patch each one
- Update ensure_claude_code() to use find_all_cli_js() for install detection
- Fixes: `claude --version` showing old version when multiple Claude Code
  installs exist (e.g. /usr/lib vs /usr/local/lib vs NVM)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
delta-cloud-208e
2026-02-26 18:39:32 +00:00
parent ae06a26974
commit ea8ce4f05d

View File

@@ -218,7 +218,8 @@ def ensure_claude_code(target_version=None):
If target_version is set and the installed version doesn't match, If target_version is set and the installed version doesn't match,
reinstall to the exact version so cli.js patch is compatible. reinstall to the exact version so cli.js patch is compatible.
""" """
cli_js = find_cli_js() all_paths = find_all_cli_js()
cli_js = all_paths[0] if all_paths else None
# If already installed, check version compatibility # If already installed, check version compatibility
if cli_js and target_version: if cli_js and target_version:
@@ -241,8 +242,8 @@ def ensure_claude_code(target_version=None):
capture_output=True, text=True, timeout=300, capture_output=True, text=True, timeout=300,
) )
if result.returncode == 0: if result.returncode == 0:
cli_js = find_cli_js() found = find_all_cli_js()
if cli_js: if found:
new_ver, _ = get_installed_version() new_ver, _ = get_installed_version()
print(f" {G}Claude Code {new_ver or ''} installed{D}") print(f" {G}Claude Code {new_ver or ''} installed{D}")
return True return True
@@ -296,6 +297,58 @@ def find_cli_js():
return None return None
def find_all_cli_js():
"""Find ALL installed Claude Code cli.js paths (for multi-install patching)."""
candidates = set()
if IS_WINDOWS:
for env_key in ("APPDATA", "LOCALAPPDATA", "PROGRAMFILES"):
base = os.environ.get(env_key, "")
if base:
candidates.add(os.path.join(base, "npm", "node_modules",
"@anthropic-ai", "claude-code", "cli.js"))
else:
# Static well-known paths
for prefix in ("/usr/lib", "/usr/local/lib", "/opt/homebrew/lib"):
candidates.add(os.path.join(prefix, "node_modules",
"@anthropic-ai", "claude-code", "cli.js"))
# npm root -g (primary install path)
try:
r = subprocess.run(["npm", "root", "-g"], capture_output=True, text=True, timeout=10)
if r.returncode == 0:
candidates.add(os.path.join(r.stdout.strip(),
"@anthropic-ai", "claude-code", "cli.js"))
except Exception:
pass
# Resolve `which claude` → follow symlinks → find cli.js
try:
r = subprocess.run(["which", "claude"], capture_output=True, text=True, timeout=5)
if r.returncode == 0:
claude_bin = os.path.realpath(r.stdout.strip())
# claude binary is at node_modules/.bin/claude
# cli.js is in node_modules/@anthropic-ai/claude-code/cli.js
nm = os.path.dirname(os.path.dirname(claude_bin)) # node_modules/
candidates.add(os.path.join(nm, "@anthropic-ai", "claude-code", "cli.js"))
except Exception:
pass
# NVM installs: /root/.nvm, /home/*/.nvm
nvm_bases = ["/root/.nvm"]
if os.path.isdir("/home"):
for user in os.listdir("/home"):
nvm_bases.append(f"/home/{user}/.nvm")
for nvm_base in nvm_bases:
versions_dir = os.path.join(nvm_base, "versions", "node")
if os.path.isdir(versions_dir):
for node_ver in os.listdir(versions_dir):
candidates.add(os.path.join(versions_dir, node_ver, "lib",
"node_modules", "@anthropic-ai", "claude-code", "cli.js"))
return [p for p in candidates if os.path.isfile(p)]
def get_installed_version(): def get_installed_version():
"""Get currently installed Claude Code version. """Get currently installed Claude Code version.
@@ -879,9 +932,12 @@ def cmd_update(force=False, settings_only=False):
# Install cli.js # Install cli.js
if not settings_only: if not settings_only:
if not cli_js: all_paths = find_all_cli_js()
cli_js = find_cli_js() if not all_paths:
if not cli_js: cli_js_single = find_cli_js()
if cli_js_single:
all_paths = [cli_js_single]
if not all_paths:
eprint(f" {R}Claude Code cli.js not found even after install attempt.{D}") eprint(f" {R}Claude Code cli.js not found even after install attempt.{D}")
return 1 return 1
@@ -890,9 +946,16 @@ def cmd_update(force=False, settings_only=False):
eprint(f" Run with: sudo python3 {sys.argv[0]}") eprint(f" Run with: sudo python3 {sys.argv[0]}")
return 1 return 1
print(f"\n{W}--- Installing cli.js v{latest} ---{D}") print(f"\n{W}--- Installing cli.js v{latest} (found {len(all_paths)} location(s)) ---{D}")
ok = install_cli_js(latest, cli_js) any_ok = False
if not ok: for path in all_paths:
print(f" Patching: {path}")
ok = install_cli_js(latest, path)
if ok:
any_ok = True
else:
eprint(f" {Y}Failed to patch {path}, continuing...{D}")
if not any_ok:
return 1 return 1
# Patch settings # Patch settings