#!/usr/bin/env bash # Claude Code — Interactive Uninstaller # # 3 modes: # 1) safe (DEFAULT) — remove only Claude Code binary + env vars + settings.json # PRESERVE all user data: projects, history, commands, # plans, file-history, plugins. # 2) full — wipe ~/.claude/ COMPLETELY (with tar backup first). # 3) settings-only — just clear settings.json (keep binary + everything else). # # Modes: # sudo bash uclaude_uninstall.sh # interactive prompt # UCLAUDE_MODE=safe sudo bash uclaude_uninstall.sh # UCLAUDE_MODE=full sudo bash uclaude_uninstall.sh # UCLAUDE_MODE=settings-only sudo bash uclaude_uninstall.sh # UCLAUDE_MODE=safe UCLAUDE_YES=1 sudo bash uclaude_uninstall.sh # no prompts # # Designed after a user lost 6 months of work to a destructive # `rm -rf ~/.claude` default. NEVER again. set -euo pipefail GREEN="\033[92m" RED="\033[91m" CYAN="\033[96m" YELLOW="\033[93m" BOLD="\033[1m" RESET="\033[0m" log() { echo -e "${GREEN}[+]${RESET} $*"; } warn() { echo -e "${YELLOW}[~]${RESET} $*"; } err() { echo -e "${RED}[!]${RESET} $*" >&2; } info() { echo -e "${CYAN}[i]${RESET} $*"; } # Cross-platform sed -i sedi() { if [[ "$(uname -s)" == "Darwin" ]]; then sed -i '' "$@" else sed -i "$@" fi } # ---------- Banner ---------- echo -e "${BOLD}" echo " +--------------------------------------+" echo " | Claude Code — Uninstaller |" echo " +--------------------------------------+" echo -e "${RESET}" # ---------- Mode selection ---------- MODE="${UCLAUDE_MODE:-}" ASSUME_YES="${UCLAUDE_YES:-0}" # Inventory user data we'll touch — show user what's at stake inventory_user_dirs() { local total_dirs=0 total_size_kb=0 for user_home in /root /home/*; do local d="$user_home/.claude" [ -d "$d" ] || continue local size_kb size_kb=$(du -sk "$d" 2>/dev/null | awk '{print $1}') total_dirs=$((total_dirs + 1)) total_size_kb=$((total_size_kb + size_kb)) local proj_count proj_count=$(find "$d/projects" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l) local cmd_count cmd_count=$(find "$d/commands" -mindepth 1 -maxdepth 1 -name '*.md' 2>/dev/null | wc -l) local plans_count plans_count=$(find "$d/plans" -mindepth 1 -maxdepth 1 2>/dev/null | wc -l) local hist_lines=0 [ -f "$d/history.jsonl" ] && hist_lines=$(wc -l <"$d/history.jsonl" 2>/dev/null || echo 0) echo " $d ($((size_kb / 1024)) MB)" echo " projects: $proj_count commands(skills): $cmd_count plans: $plans_count history lines: $hist_lines" done echo echo " Total: $total_dirs user dir(s), $((total_size_kb / 1024)) MB" } prompt_mode() { echo -e "${BOLD}What to uninstall?${RESET}" echo echo -e " ${GREEN}1) safe ${RESET}— remove Claude Code binary + env vars + settings.json" echo -e " ${BOLD}KEEP user data${RESET}: projects, history, commands(skills)," echo " plans, file-history, plugins." echo " (recommended — reversible)" echo echo -e " ${YELLOW}2) settings-only ${RESET}— just clear settings.json env block." echo " Binary stays, all user data stays." echo " (use to fix bad env vars without removing claude)" echo echo -e " ${RED}3) full ${RESET}— wipe entire ~/.claude/ directory + binary + env vars." echo -e " ${BOLD}DESTROYS all user data${RESET} (tar backup created first)." echo " (use only when you're SURE; backup .tar.gz lives in \$HOME)" echo echo " 4) cancel — exit, do nothing" echo echo -e "${BOLD}Your data right now:${RESET}" inventory_user_dirs echo while true; do read -r -p "$(echo -e "${BOLD}Choose [1/2/3/4] (default 1=safe):${RESET} ")" choice case "${choice:-1}" in 1|safe) MODE="safe"; break ;; 2|settings-only) MODE="settings-only"; break ;; 3|full) MODE="full" # Triple-confirm full wipe echo warn "FULL mode will DELETE all conversations, custom skills, plans, history." warn "A tar backup will be created in each user's home directory before delete." read -r -p "$(echo -e "${RED}${BOLD}Type 'WIPE' to confirm full wipe:${RESET} ")" confirm [ "$confirm" = "WIPE" ] && break err "Confirmation failed — switching back to mode select" ;; 4|cancel|q|exit) info "Cancelled, no changes made."; exit 0 ;; *) echo "Invalid choice — pick 1, 2, 3 or 4." ;; esac done } # Decide mode: env override → CLI arg → interactive prompt if [ -n "$MODE" ]; then case "$MODE" in safe|settings-only|full) info "Mode (from UCLAUDE_MODE env): $MODE" ;; *) err "Invalid UCLAUDE_MODE='$MODE' (valid: safe, settings-only, full)"; exit 2 ;; esac elif [ "$ASSUME_YES" = "1" ]; then MODE="safe" info "UCLAUDE_YES=1 + no UCLAUDE_MODE — defaulting to 'safe'" else prompt_mode fi TIMESTAMP=$(date +%Y%m%d%H%M%S) echo info "Mode: ${BOLD}${MODE}${RESET}" echo # ---------- npm package removal (all modes except settings-only) ---------- if [ "$MODE" != "settings-only" ]; then if npm list -g @anthropic-ai/claude-code &>/dev/null 2>&1 || command -v claude >/dev/null 2>&1; then info "Removing @anthropic-ai/claude-code npm package..." npm uninstall -g @anthropic-ai/claude-code 2>/dev/null || true # Cleanup symlink if npm didn't (system installs) for binpath in /usr/bin/claude /usr/local/bin/claude /opt/homebrew/bin/claude; do if [ -L "$binpath" ] || [ -f "$binpath" ]; then # Verify it points to OUR install before removing if [ -L "$binpath" ] && readlink "$binpath" | grep -q "claude-code"; then rm -f "$binpath" log "Removed symlink: $binpath" fi fi done log "Claude Code binary removed" else warn "Claude Code not found in npm global packages" fi fi # ---------- ~/.claude/ handling per mode ---------- for user_home in /root /home/*; do CLAUDE_DIR="$user_home/.claude" [ -d "$CLAUDE_DIR" ] || continue # ALWAYS create full tar backup BEFORE any destructive operation, in EVERY mode. # Per user demand after data loss incident: never trust mode flags alone. # If backup fails, refuse to touch this user's data and skip to next user. FULL_BACKUP_TAR="$user_home/.claude.uninstall-backup.${TIMESTAMP}.tar.gz" info "Creating safety backup: $FULL_BACKUP_TAR ..." if ! tar -czf "$FULL_BACKUP_TAR" -C "$user_home" .claude 2>/dev/null; then err "Backup FAILED for $CLAUDE_DIR — REFUSING to proceed (data preserved)" continue fi backup_size=$(du -h "$FULL_BACKUP_TAR" 2>/dev/null | awk '{print $1}') log "Safety backup: $FULL_BACKUP_TAR ($backup_size)" log "Restore anytime: tar -xzf $FULL_BACKUP_TAR -C $user_home" # Also back up ~/.claude.json (onboarding state lives outside .claude/) if [ -f "$user_home/.claude.json" ]; then cp -p "$user_home/.claude.json" "$user_home/.claude.json.uninstall.bak.${TIMESTAMP}" 2>/dev/null || true fi case "$MODE" in safe) # Remove ONLY patcher-managed files. Preserve all user data. info "Cleaning $CLAUDE_DIR (preserving user data)..." for f in settings.json settings.local.json .patcher.config.cache.json; do if [ -f "$CLAUDE_DIR/$f" ]; then cp -p "$CLAUDE_DIR/$f" "$CLAUDE_DIR/${f}.uninstall.bak.${TIMESTAMP}" rm -f "$CLAUDE_DIR/$f" log "Removed $CLAUDE_DIR/$f (backup: ${f}.uninstall.bak.${TIMESTAMP})" fi done # cache/ is regenerated by Claude — safe to clear if [ -d "$CLAUDE_DIR/cache" ]; then rm -rf "$CLAUDE_DIR/cache" log "Cleared $CLAUDE_DIR/cache (will regenerate on next run)" fi log "PRESERVED: projects/ history.jsonl commands/ plans/ file-history/ plugins/" ;; settings-only) # Even softer — only settings.json, nothing else for f in settings.json settings.local.json; do if [ -f "$CLAUDE_DIR/$f" ]; then cp -p "$CLAUDE_DIR/$f" "$CLAUDE_DIR/${f}.uninstall.bak.${TIMESTAMP}" rm -f "$CLAUDE_DIR/$f" log "Removed $CLAUDE_DIR/$f (backup: ${f}.uninstall.bak.${TIMESTAMP})" fi done ;; full) # Backup already created above. Now wipe. rm -rf "$CLAUDE_DIR" log "Removed $CLAUDE_DIR (full wipe)" ;; esac # ~/.claude.json (onboarding state) — backup-and-remove in full+safe modes CLAUDE_JSON="$user_home/.claude.json" if [ -f "$CLAUDE_JSON" ] && [ "$MODE" != "settings-only" ]; then cp -p "$CLAUDE_JSON" "${CLAUDE_JSON}.uninstall.bak.${TIMESTAMP}" rm -f "$CLAUDE_JSON" log "Removed $CLAUDE_JSON (backup: $(basename "$CLAUDE_JSON").uninstall.bak.${TIMESTAMP})" fi done # ---------- env vars + shell rc files (skip in settings-only mode) ---------- if [ "$MODE" != "settings-only" ]; then for user_home in /root /home/*; do for rc_file in "$user_home/.bashrc" "$user_home/.zshrc"; do if [ -f "$rc_file" ] && grep -q 'ANTHROPIC_API_KEY\|CLAUDE_CODE\|Claude Code\|ANTHROPIC_BASE_URL\|ANTHROPIC_AUTH_TOKEN' "$rc_file" 2>/dev/null; then # Backup rc file before mutating (sed -i destructive) cp -p "$rc_file" "${rc_file}.uninstall.bak.${TIMESTAMP}" info "Cleaning env vars from $rc_file (backup: $(basename "$rc_file").uninstall.bak.${TIMESTAMP})..." sedi '/# Claude Code/d' "$rc_file" sedi '/# UnlimitedCoding.*[Cc]laude/d' "$rc_file" sedi '/ANTHROPIC_API_KEY/d' "$rc_file" sedi '/ANTHROPIC_AUTH_TOKEN/d' "$rc_file" sedi '/ANTHROPIC_BASE_URL/d' "$rc_file" sedi '/CLAUDE_CODE/d' "$rc_file" log "Cleaned $rc_file" fi done done for f in /etc/profile.d/claude-code.sh /etc/profile.d/claude_code.sh; do if [ -f "$f" ]; then cp -p "$f" "${f}.uninstall.bak.${TIMESTAMP}" 2>/dev/null rm -f "$f" log "Removed $f" fi done if [ -f "/etc/environment" ] && grep -q 'ANTHROPIC_API_KEY\|ANTHROPIC_AUTH_TOKEN\|ANTHROPIC_BASE_URL\|CLAUDE_CODE' /etc/environment 2>/dev/null; then cp -p /etc/environment "/etc/environment.uninstall.bak.${TIMESTAMP}" info "Cleaning /etc/environment (backup: /etc/environment.uninstall.bak.${TIMESTAMP})..." sedi '/ANTHROPIC_API_KEY/d' /etc/environment sedi '/ANTHROPIC_AUTH_TOKEN/d' /etc/environment sedi '/ANTHROPIC_BASE_URL/d' /etc/environment sedi '/CLAUDE_CODE/d' /etc/environment log "Cleaned /etc/environment" fi info "Removing npm registry config..." npm config delete @anthropic-ai:registry 2>/dev/null || true log "npm registry config removed" fi echo case "$MODE" in safe) echo -e "${GREEN}${BOLD} Done — Claude Code removed, your data preserved.${RESET}" echo echo " Reinstall keeps everything: just re-run uclaude_install.sh" echo " Backups (settings only): ~/.claude/*.uninstall.bak.${TIMESTAMP}" ;; settings-only) echo -e "${GREEN}${BOLD} Settings cleared — binary + user data untouched.${RESET}" echo echo " Re-apply patches: re-run uclaude_install.sh or uclaude_updater.py" ;; full) echo -e "${YELLOW}${BOLD} Full wipe complete.${RESET}" echo echo " Restore backup: tar -xzf ~/.claude.uninstall-backup.${TIMESTAMP}.tar.gz -C ~" ;; esac echo