Skip to content

Instantly share code, notes, and snippets.

@marceldarvas
Last active April 20, 2026 14:34
Show Gist options
  • Select an option

  • Save marceldarvas/9e10fd41d608bdb1ba277b7f989b4763 to your computer and use it in GitHub Desktop.

Select an option

Save marceldarvas/9e10fd41d608bdb1ba277b7f989b4763 to your computer and use it in GitHub Desktop.
Pin Claude Code to a specific npm version (bypassing Homebrew) or restore to Homebrew-managed latest
#!/bin/bash
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Claude Code Version Pin
# @raycast.mode fullOutput
# @raycast.argument1 { "type": "dropdown", "placeholder": "Action", "data": [{"title": "Pin", "value": "pin"}, {"title": "Restore", "value": "restore"}] }
# Optional parameters:
# @raycast.icon 🔧
# Documentation:
# @raycast.description Pin Claude Code to a specific npm version or restore to original install method (Homebrew / self-managed).
# @raycast.author marceldarvas
# @raycast.authorURL https://raycast.com/marceldarvas
# --- Configuration ---
PINNED_VERSION="2.1.66"
ZSHRC="$HOME/.zshrc"
AUTO_UPDATE_VAR="CLAUDE_CODE_DISABLE_AUTO_UPDATE"
MARKER="# claude-code-version-pin managed"
STATE_FILE="$HOME/.claude-code-pin-state"
set -o pipefail
# --- Helpers ---
info() { echo "ℹ️ $*"; }
ok() { echo "$*"; }
warn() { echo "⚠️ $*"; }
fail() { echo "$*"; exit 1; }
current_version() {
claude --version 2>/dev/null | head -1
}
# Detect how Claude Code is currently installed
# Returns: "homebrew", "self-managed", "npm", or "unknown"
detect_install_method() {
local claude_path
claude_path=$(which claude 2>/dev/null)
if [ -z "$claude_path" ]; then
echo "unknown"
return
fi
# Resolve symlinks
local resolved
resolved=$(readlink -f "$claude_path" 2>/dev/null || readlink "$claude_path" 2>/dev/null || echo "$claude_path")
# Check Homebrew
if brew list claude-code &>/dev/null && [[ "$resolved" == *"Cellar"* || "$resolved" == *"homebrew"* ]]; then
echo "homebrew"
# Check self-managed (~/.local/share/claude/versions/)
elif [[ "$resolved" == *".local/share/claude/versions"* ]]; then
echo "self-managed"
# Check npm global
elif [[ "$resolved" == *"node_modules"* || "$resolved" == *"npm"* || "$resolved" == *"lib/node_modules"* ]]; then
echo "npm"
# Fallback: check if Homebrew owns it even without Cellar in path
elif brew list claude-code &>/dev/null; then
echo "homebrew"
else
echo "unknown"
fi
}
pin() {
info "Current version: $(current_version)"
# 1. Detect current install method and save for restore
INSTALL_METHOD=$(detect_install_method)
info "Detected install method: ${INSTALL_METHOD}"
echo "$INSTALL_METHOD" > "$STATE_FILE"
ok "Saved original install method to ${STATE_FILE}"
# 2. Handle current install method
case "$INSTALL_METHOD" in
homebrew)
info "Unlinking Homebrew claude-code..."
brew unlink claude-code || fail "Failed to unlink Homebrew claude-code"
ok "Homebrew claude-code unlinked (formula kept for later restore)"
;;
self-managed)
info "Self-managed install detected at ~/.local/share/claude/versions/"
info "npm install will take PATH priority once auto-update is disabled"
;;
npm)
info "Already npm-managed, will overwrite with pinned version"
;;
*)
warn "Could not determine install method — proceeding anyway"
;;
esac
# 3. Install pinned version via npm
info "Installing claude-code@${PINNED_VERSION} via npm..."
npm install -g "@anthropic-ai/claude-code@${PINNED_VERSION}" || fail "npm install failed"
ok "Installed claude-code@${PINNED_VERSION}"
# 4. Disable auto-update in .zshrc
if grep -q "$AUTO_UPDATE_VAR" "$ZSHRC" 2>/dev/null; then
info "Auto-update disable already present in .zshrc"
else
info "Adding auto-update disable to .zshrc..."
echo "" >> "$ZSHRC"
echo "export ${AUTO_UPDATE_VAR}=1 ${MARKER}" >> "$ZSHRC"
ok "Auto-update disabled"
fi
# Export for current shell too
export "${AUTO_UPDATE_VAR}=1"
# 5. Verify
info "Verifying installation..."
WHICH=$(which claude 2>/dev/null)
VERSION=$(current_version)
ok "claude binary: ${WHICH}"
ok "Version: ${VERSION}"
echo ""
ok "Claude Code pinned to ${PINNED_VERSION}"
info "Restart any open Claude sessions to pick up the new version."
info "Run with 'restore' once the fix ships to return to ${INSTALL_METHOD} management."
}
restore() {
info "Current version: $(current_version)"
# 1. Determine original install method
if [ -f "$STATE_FILE" ]; then
ORIGINAL_METHOD=$(cat "$STATE_FILE")
info "Original install method (from state file): ${ORIGINAL_METHOD}"
else
ORIGINAL_METHOD=$(detect_install_method)
warn "No state file found — detected current method: ${ORIGINAL_METHOD}"
fi
# 2. Remove npm global version
info "Removing npm global claude-code..."
npm uninstall -g @anthropic-ai/claude-code 2>/dev/null
ok "npm claude-code removed"
# 3. Restore based on original install method
case "$ORIGINAL_METHOD" in
homebrew)
info "Restoring Homebrew-managed claude-code..."
if brew list claude-code &>/dev/null; then
brew link claude-code || fail "Failed to link Homebrew claude-code"
else
brew install claude-code || fail "Homebrew install failed"
fi
ok "Homebrew claude-code restored"
;;
self-managed)
info "Restoring self-managed claude-code..."
# The self-managed binary at ~/.local/bin/claude should already be on PATH
# once npm version is removed. Auto-update will resume and handle versioning.
if [ -L "$HOME/.local/bin/claude" ] || [ -f "$HOME/.local/bin/claude" ]; then
ok "Self-managed binary still present at ~/.local/bin/claude"
info "Auto-update will bring it to latest on next launch"
else
warn "Self-managed binary not found — you may need to reinstall Claude Code"
info "Reinstall: curl -fsSL https://claude.ai/install.sh | sh"
fi
;;
npm)
info "Reinstalling latest claude-code via npm..."
npm install -g @anthropic-ai/claude-code || fail "npm install failed"
ok "npm claude-code restored to latest"
;;
*)
warn "Unknown original method '${ORIGINAL_METHOD}' — leaving npm uninstalled"
info "Reinstall manually via your preferred method"
;;
esac
# 4. Remove auto-update disable from .zshrc
if grep -q "$MARKER" "$ZSHRC" 2>/dev/null; then
info "Removing auto-update disable from .zshrc..."
sed -i '' "/${MARKER}/d" "$ZSHRC"
# Clean up any trailing blank line left behind
sed -i '' -e :a -e '/^\n*$/{$d;N;ba' -e '}' "$ZSHRC"
ok "Auto-update re-enabled"
fi
unset "$AUTO_UPDATE_VAR"
# 5. Clean up state file
rm -f "$STATE_FILE"
# 6. Verify
info "Verifying installation..."
WHICH=$(which claude 2>/dev/null)
VERSION=$(current_version)
ok "claude binary: ${WHICH}"
ok "Version: ${VERSION}"
echo ""
ok "Claude Code restored to ${ORIGINAL_METHOD} management"
info "Restart any open Claude sessions to pick up the new version."
}
# --- Main ---
ACTION="${1:-}"
case "$ACTION" in
pin) pin ;;
restore) restore ;;
*)
echo "Usage: $(basename "$0") <pin|restore>"
echo ""
echo " pin — Downgrade to claude-code@${PINNED_VERSION} via npm, disable auto-update"
echo " restore — Remove npm version, restore Homebrew, re-enable auto-update"
exit 1
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment