From 8dc4027c4ba390e014c83e9c3d47cf2b0fd74642 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Wed, 1 Jul 2026 16:07:13 +0200 Subject: [PATCH] fix(install): make Claude Code install/update idempotent across channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install.sh aborted with npm EEXIST when claude was already present: the binary is a native-installer symlink (~/.local/bin/claude -> ~/.local/share/claude/versions/*) that npm does not own, and the npm prefix (~/.local, set for BLK-013) targets the same path. The `else err` branch turned EEXIST into a fatal exit. No presence guard existed, unlike the RTK/GSD steps. - install.sh: skip-if-present guard (command -v claude), mirroring the RTK/GSD pattern; npm only runs on a truly fresh machine. - update-all.sh: pick updater by channel — npm for npm-managed installs, `claude update` for native installs (npm would EEXIST). Co-Authored-By: Claude --- install.sh | 10 ++++++++-- update-all.sh | 12 ++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/install.sh b/install.sh index 7187feb..84fea19 100755 --- a/install.sh +++ b/install.sh @@ -54,9 +54,15 @@ ok "npm $(npm -v)" # ── 2. Install Claude Code CLI ── echo "" -echo "── Installing Claude Code (latest)..." +echo "── Installing Claude Code..." -if npm install -g @anthropic-ai/claude-code@latest; then +# Idempotent: an existing claude (native installer under ~/.local/share/claude, +# or any prior install) already owns ~/.local/bin/claude — npm cannot clobber a +# symlink it does not manage (EEXIST). Mirror the RTK/GSD skip-if-present guard; +# upgrades are `make update`'s job (update-all.sh), not first-time install. +if command -v claude &>/dev/null; then + ok "Claude Code already installed ($(claude --version 2>/dev/null | head -1))" +elif npm install -g @anthropic-ai/claude-code@latest; then ok "Claude Code installed: $(claude --version 2>/dev/null || echo 'unknown')" else err "Claude Code installation failed" diff --git a/update-all.sh b/update-all.sh index 28fbc84..621e8ed 100644 --- a/update-all.sh +++ b/update-all.sh @@ -27,7 +27,15 @@ echo "── Updating Claude Code CLI..." if command -v claude &>/dev/null; then CURRENT_VER=$(claude --version 2>/dev/null | head -1 || echo "unknown") info "Current: $CURRENT_VER" - if npm install -g @anthropic-ai/claude-code@latest 2>/dev/null; then + # Use the updater that matches the install channel: npm-managed installs + # update via npm; native-installer installs self-update via `claude update` + # (npm would EEXIST on the ~/.local/bin/claude symlink it does not own). + if npm ls -g @anthropic-ai/claude-code &>/dev/null; then + UPDATE_CMD=(npm install -g @anthropic-ai/claude-code@latest) + else + UPDATE_CMD=(claude update) + fi + if "${UPDATE_CMD[@]}" &>/dev/null; then NEW_VER=$(claude --version 2>/dev/null | head -1 || echo "unknown") if [ "$CURRENT_VER" = "$NEW_VER" ]; then ok "Claude Code already up to date ($NEW_VER)" @@ -35,7 +43,7 @@ if command -v claude &>/dev/null; then ok "Claude Code updated: $CURRENT_VER → $NEW_VER" fi else - warn "Claude Code update failed — try manually: npm install -g @anthropic-ai/claude-code@latest" + warn "Claude Code update failed — try manually: ${UPDATE_CMD[*]}" fi else warn "Claude Code not found — install first with: make install"