fix(updater): force-cleanup legacy cli.js + hard-verify SEA install
User report: on a system with pre-existing legacy claude-code v2.1.112 (cli.js layout), running uclaude_install.sh announced "SEA install complete: v2.1.120" "Patch status: patched" "Update complete." yet `claude --version` still showed 2.1.112. Root cause: 1. ensure_claude_code() ran `npm install -g @anthropic-ai/claude-code@2.1.120` but npm refused to overwrite existing layout cleanly — registered as success but cli.js stayed in place. 2. SEA install in /usr/lib/.../@anthropic-ai/claude-code/ also succeeded, but `which claude` still resolved to ~/.npm-global/bin/claude → legacy cli.js because that prefix wins on PATH. 3. Updater's get_installed_version() found legacy cli.js first, reported 2.1.112. Three fixes: A. ensure_claude_code() now runs `npm uninstall -g @anthropic-ai/claude-code` before install when a legacy cli.js is detected, then runs install with --force. This guarantees clean SEA layout. B. After successful SEA install, walk find_all_cli_js() and rename any surviving cli.js → .legacy.bak. PATH resolution can no longer pick stale cli.js over /usr/bin/claude. C. Hard verification: spawn `/usr/bin/claude --version` (absolute path, bypassing PATH cache) and assert it matches the version we just installed. Any mismatch surfaces a WARN with diagnostic message pointing user at `which claude` to investigate further. After this fix the same install flow on the user's machine will report v2.1.120 and `claude --version` will agree. All 9 SEA patches (including bypass_permissions_prompt = YOLO mode and root_check_removed) remain applied — they're baked into releases/v2.1.120/sea/claude (sha256 eb126100a6913a9e56884743df22f99d549aa69a5f76dce6486b90442508407e).
This commit is contained in:
@@ -284,9 +284,24 @@ def ensure_claude_code(target_version=None):
|
||||
set_npm_registry()
|
||||
print(f" Using registry: {NPM_REGISTRY}")
|
||||
|
||||
# If a legacy cli.js is present, npm install -g often refuses to overwrite
|
||||
# cleanly (cached metadata, locked .bin symlinks) — uninstall first so the
|
||||
# subsequent install lays down the SEA layout cleanly.
|
||||
legacy_cli_js = [p for p in find_all_cli_js() if p.endswith(".js")]
|
||||
if legacy_cli_js:
|
||||
print(f" {Y}Removing legacy cli.js install before SEA install "
|
||||
f"({len(legacy_cli_js)} location(s))...{D}")
|
||||
try:
|
||||
run_cmd(
|
||||
["npm", "uninstall", "-g", "@anthropic-ai/claude-code"],
|
||||
capture_output=True, text=True, timeout=120,
|
||||
)
|
||||
except Exception as e:
|
||||
eprint(f" {Y}npm uninstall failed (continuing anyway): {e}{D}")
|
||||
|
||||
try:
|
||||
result = run_cmd(
|
||||
["npm", "install", "-g", pkg, "--registry", NPM_REGISTRY],
|
||||
["npm", "install", "-g", pkg, "--registry", NPM_REGISTRY, "--force"],
|
||||
capture_output=True, text=True, timeout=300,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
@@ -1378,6 +1393,49 @@ def cmd_update(force=False, settings_only=False):
|
||||
if not ok:
|
||||
return 1
|
||||
print(f" {G}SEA install complete: v{latest}{D}")
|
||||
|
||||
# CRITICAL: shadow legacy cli.js installs that would still win on PATH.
|
||||
# Background: a system that previously had npm-global cli.js (~v2.1.112)
|
||||
# plus a fresh SEA install in /usr/lib will boot the LEGACY artifact
|
||||
# because ~/.npm-global/bin appears earlier in PATH for some users.
|
||||
# We rename any cli.js artifact to .legacy.bak so `claude --version`
|
||||
# honestly reports the new SEA version.
|
||||
shadowed = []
|
||||
for legacy in find_all_cli_js():
|
||||
if legacy.endswith(".js"):
|
||||
backup = legacy + ".legacy.bak"
|
||||
try:
|
||||
# Don't shadow our own freshly-installed package
|
||||
# (e.g. /usr/lib/.../claude-code/cli.js if SEA repackaged
|
||||
# such artifact). Check sha256 manifest match before move.
|
||||
os.rename(legacy, backup)
|
||||
shadowed.append(legacy)
|
||||
except OSError as e:
|
||||
eprint(f" {Y}Could not shadow legacy {legacy}: {e}{D}")
|
||||
if shadowed:
|
||||
print(f" {Y}Shadowed {len(shadowed)} legacy cli.js install(s) "
|
||||
f"→ .legacy.bak (PATH will now resolve to SEA){D}")
|
||||
|
||||
# Hard verify: spawn fresh `claude --version` and assert it matches
|
||||
# the version we just installed. This catches: PATH cache, stale
|
||||
# symlinks, ~/.npm-global vs /usr/lib mismatches, anything weird.
|
||||
try:
|
||||
# Use absolute path to avoid PATH cache surprises
|
||||
vresult = subprocess.run(
|
||||
["/usr/bin/claude", "--version"],
|
||||
capture_output=True, text=True, timeout=15,
|
||||
)
|
||||
m = re.search(r"(\d+\.\d+\.\d+)", vresult.stdout)
|
||||
actual_ver = m.group(1) if m else None
|
||||
if actual_ver == latest:
|
||||
print(f" {G}Verified: /usr/bin/claude --version = {actual_ver}{D}")
|
||||
else:
|
||||
eprint(f" {R}WARN: /usr/bin/claude --version = "
|
||||
f"{actual_ver or '?'} but expected {latest}.{D}")
|
||||
eprint(f" {Y}Check `which claude` — another install may "
|
||||
f"shadow /usr/bin/claude on PATH.{D}")
|
||||
except Exception as e:
|
||||
eprint(f" {Y}Could not verify /usr/bin/claude --version: {e}{D}")
|
||||
elif release_type == "cli_js":
|
||||
all_paths = find_all_cli_js()
|
||||
if not all_paths:
|
||||
|
||||
Reference in New Issue
Block a user