Last active
May 5, 2026 09:53
-
-
Save nguyenvanduocit/a258aedf48c1cab6de8e67b76e5ef3b5 to your computer and use it in GitHub Desktop.
Hoàn toàn gỡ Claude Code CLI + mọi cấu hình ~/.claude (dry-run by default)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # uninstall-claude-code.sh | |
| # | |
| # Hoàn toàn gỡ Claude Code CLI và mọi cấu hình trên macOS / Linux. | |
| # Mặc định chạy ở chế độ dry-run (chỉ in, không xoá). | |
| # Dùng --execute để thực sự xoá. | |
| # | |
| # Quét phổ rộng: | |
| # - Multi-package-manager: npm, bun, pnpm, yarn | |
| # - Multi-Node version manager: nvm, fnm, volta, n | |
| # - Cross-platform: macOS Library/* + Linux XDG paths | |
| # - Homebrew bins: /usr/local/bin, /opt/homebrew/bin | |
| # - PATH cross-check: `which -a claude` cuối cùng | |
| # | |
| # An toàn theo thiết kế: | |
| # - Dry-run mặc định | |
| # - Mọi path phải nằm trong $HOME (trừ các bin dir hệ thống được whitelist) | |
| # - In rõ từng path/lệnh; path không tồn tại → skip im lặng | |
| set -uo pipefail | |
| # ---------- Defaults ---------- | |
| DRY_RUN=true | |
| INCLUDE_DESKTOP=false | |
| KEEP_PM=false | |
| REMOVE_COMPLETIONS=false | |
| PURGE_KEYCHAIN=false | |
| ASSUME_YES=false | |
| # ---------- Colors ---------- | |
| if [[ -t 1 ]]; then | |
| C_RED=$'\033[31m'; C_GRN=$'\033[32m'; C_YEL=$'\033[33m' | |
| C_BLU=$'\033[34m'; C_DIM=$'\033[2m'; C_RST=$'\033[0m' | |
| else | |
| C_RED=""; C_GRN=""; C_YEL=""; C_BLU=""; C_DIM=""; C_RST="" | |
| fi | |
| # ---------- OS detect ---------- | |
| OS="$(uname -s)" | |
| IS_MAC=false; IS_LINUX=false | |
| case "$OS" in | |
| Darwin) IS_MAC=true ;; | |
| Linux) IS_LINUX=true ;; | |
| esac | |
| # ---------- Helpers ---------- | |
| usage() { | |
| cat <<EOF | |
| Usage: $(basename "$0") [OPTIONS] | |
| Gỡ hoàn toàn Claude Code CLI và mọi cấu hình (macOS / Linux). | |
| OPTIONS: | |
| -e, --execute Thực sự xoá (mặc định: dry-run) | |
| --include-desktop Xoá luôn dữ liệu Claude Desktop app | |
| --keep-pm Bỏ qua bước npm/bun/pnpm/yarn uninstall | |
| --remove-completions Xoá shell completion files (fish/zsh/bash) | |
| --purge-keychain Xoá entries Keychain (macOS only, OAuth tokens) | |
| -y, --yes Không hỏi xác nhận khi --execute | |
| -h, --help In trợ giúp này | |
| VÍ DỤ: | |
| $(basename "$0") # dry-run, xem những gì sẽ bị xoá | |
| $(basename "$0") --execute # xoá thật, có hỏi xác nhận | |
| $(basename "$0") --execute -y # xoá thật, không hỏi | |
| $(basename "$0") --execute --include-desktop --remove-completions --purge-keychain | |
| # gỡ tận gốc | |
| QUÉT NHỮNG GÌ: | |
| Config: ~/.claude, ~/.claude.json{,.backup,.tmp.*} | |
| Linux XDG: ~/.config/claude*, ~/.local/share/claude*, ~/.local/state/claude* | |
| Cache: macOS: ~/Library/Caches/claude-cli-nodejs, ~/Library/Caches/com.anthropic.claude-code | |
| Linux: ~/.cache/claude* | |
| Binary: npm/bun/pnpm/yarn global, nvm/fnm/volta/n bin dirs, | |
| ~/.local/bin, ~/.npm-global/bin, /usr/local/bin, /opt/homebrew/bin | |
| PATH: \`which -a claude\` cross-check cuối | |
| +Desktop:~/Library/Application Support/Claude (macOS), ~/.config/Claude (Linux) | |
| +Comp: fish/zsh/bash completion files | |
| +Keychn: security service "Claude Code", "claude.ai" (macOS) | |
| EOF | |
| } | |
| log() { echo "${C_BLU}[INFO]${C_RST} $*"; } | |
| warn() { echo "${C_YEL}[WARN]${C_RST} $*"; } | |
| err() { echo "${C_RED}[ERR ]${C_RST} $*" >&2; } | |
| ok() { echo "${C_GRN}[ OK ]${C_RST} $*"; } | |
| dim() { echo "${C_DIM}$*${C_RST}"; } | |
| # Whitelist path roots cho phép xoá. $HOME mặc định an toàn. | |
| # Thêm các bin dir hệ thống mà Claude có thể cài vào (Homebrew prefix). | |
| WHITELIST_ROOTS=( | |
| "$HOME" | |
| "/usr/local/bin" | |
| "/usr/local/share/zsh/site-functions" | |
| "/opt/homebrew/bin" | |
| "/opt/homebrew/share/zsh/site-functions" | |
| ) | |
| is_safe_path() { | |
| local p="$1" | |
| for root in "${WHITELIST_ROOTS[@]}"; do | |
| case "$p" in | |
| "$root"|"$root"/*) return 0 ;; | |
| esac | |
| done | |
| return 1 | |
| } | |
| remove_path() { | |
| local target="$1" | |
| local expanded=() | |
| if [[ "$target" == *"*"* ]]; then | |
| # shellcheck disable=SC2206 | |
| expanded=( $target ) | |
| if [[ ${#expanded[@]} -eq 1 && ! -e "${expanded[0]}" && ! -L "${expanded[0]}" ]]; then | |
| return 0 | |
| fi | |
| else | |
| expanded=( "$target" ) | |
| fi | |
| for p in "${expanded[@]}"; do | |
| if [[ ! -e "$p" && ! -L "$p" ]]; then | |
| continue | |
| fi | |
| if ! is_safe_path "$p"; then | |
| err " TỪ CHỐI (ngoài whitelist): $p" | |
| continue | |
| fi | |
| local size="" | |
| if [[ -d "$p" && ! -L "$p" ]]; then | |
| size=$(du -sh "$p" 2>/dev/null | awk '{print $1}') | |
| [[ -n "$size" ]] && size=" (${size})" | |
| elif [[ -f "$p" || -L "$p" ]]; then | |
| size=$(du -h "$p" 2>/dev/null | awk '{print $1}') | |
| [[ -n "$size" ]] && size=" (${size})" | |
| fi | |
| if $DRY_RUN; then | |
| echo " ${C_YEL}[DRY]${C_RST} would rm -rf ${p}${size}" | |
| else | |
| rm -rf -- "$p" && ok " removed: ${p}${size}" || err " failed: $p" | |
| fi | |
| done | |
| } | |
| run_cmd() { | |
| local desc="$1"; shift | |
| if $DRY_RUN; then | |
| echo " ${C_YEL}[DRY]${C_RST} would run: ${desc}" | |
| dim " $*" | |
| else | |
| log " running: $desc" | |
| if "$@" >/dev/null 2>&1; then | |
| ok " done: $desc" | |
| else | |
| warn " failed (non-fatal): $desc" | |
| fi | |
| fi | |
| } | |
| confirm() { | |
| $ASSUME_YES && return 0 | |
| $DRY_RUN && return 0 | |
| echo | |
| read -r -p "${C_RED}Thật sự xoá những thứ trên? Gõ 'yes' để xác nhận: ${C_RST}" ans | |
| [[ "$ans" == "yes" ]] | |
| } | |
| # Trả về 0 nếu binary `claude` đăng ký với package manager, 1 nếu không | |
| pm_has_claude() { | |
| local pm="$1" | |
| case "$pm" in | |
| npm) | |
| command -v npm >/dev/null 2>&1 && \ | |
| npm list -g --depth=0 2>/dev/null | grep -q "@anthropic-ai/claude-code" | |
| ;; | |
| bun) | |
| command -v bun >/dev/null 2>&1 && \ | |
| bun pm ls -g 2>/dev/null | grep -q "@anthropic-ai/claude-code" | |
| ;; | |
| pnpm) | |
| command -v pnpm >/dev/null 2>&1 && \ | |
| pnpm list -g --depth=0 2>/dev/null | grep -q "@anthropic-ai/claude-code" | |
| ;; | |
| yarn) | |
| command -v yarn >/dev/null 2>&1 && \ | |
| yarn global list 2>/dev/null | grep -q "@anthropic-ai/claude-code" | |
| ;; | |
| *) return 1 ;; | |
| esac | |
| } | |
| # ---------- Parse args ---------- | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -e|--execute) DRY_RUN=false; shift ;; | |
| --include-desktop) INCLUDE_DESKTOP=true; shift ;; | |
| --keep-pm|--keep-npm) KEEP_PM=true; shift ;; | |
| --remove-completions) REMOVE_COMPLETIONS=true; shift ;; | |
| --purge-keychain) PURGE_KEYCHAIN=true; shift ;; | |
| -y|--yes) ASSUME_YES=true; shift ;; | |
| -h|--help) usage; exit 0 ;; | |
| *) err "Tham số không hợp lệ: $1"; usage; exit 1 ;; | |
| esac | |
| done | |
| # ---------- Banner ---------- | |
| echo | |
| if $DRY_RUN; then | |
| echo "${C_YEL}=== DRY-RUN MODE — KHÔNG XOÁ GÌ HẾT ===${C_RST}" | |
| dim "OS: $OS | --execute để xoá thật" | |
| else | |
| echo "${C_RED}=== EXECUTE MODE — SẼ XOÁ THẬT ===${C_RST}" | |
| dim "OS: $OS" | |
| fi | |
| echo | |
| # ============================================================ | |
| # Section 1 — Config & data | |
| # ============================================================ | |
| log "[1] Cấu hình & data Claude Code" | |
| remove_path "$HOME/.claude" | |
| remove_path "$HOME/.claude.json" | |
| remove_path "$HOME/.claude.json.backup" | |
| remove_path "$HOME/.claude.json.tmp.*" | |
| # XDG (cross-platform, chuẩn hơn trên Linux) | |
| XDG_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}" | |
| XDG_DATA="${XDG_DATA_HOME:-$HOME/.local/share}" | |
| XDG_STATE="${XDG_STATE_HOME:-$HOME/.local/state}" | |
| XDG_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}" | |
| remove_path "$XDG_CONFIG/claude" | |
| remove_path "$XDG_CONFIG/claude-code" | |
| remove_path "$XDG_CONFIG/anthropic" | |
| remove_path "$XDG_DATA/claude" | |
| remove_path "$XDG_DATA/claude-code" | |
| remove_path "$XDG_STATE/claude" | |
| remove_path "$XDG_STATE/claude-code" | |
| # ============================================================ | |
| # Section 2 — Cache | |
| # ============================================================ | |
| echo | |
| log "[2] Cache" | |
| if $IS_MAC; then | |
| remove_path "$HOME/Library/Caches/claude-cli-nodejs" | |
| remove_path "$HOME/Library/Caches/com.anthropic.claude-code" | |
| remove_path "$HOME/Library/Caches/com.anthropic.claude" | |
| fi | |
| remove_path "$XDG_CACHE/claude" | |
| remove_path "$XDG_CACHE/claude-code" | |
| remove_path "$XDG_CACHE/claude-cli-nodejs" | |
| # ============================================================ | |
| # Section 3 — Claude Desktop (opt-in) | |
| # ============================================================ | |
| echo | |
| if $INCLUDE_DESKTOP; then | |
| log "[3] Claude Desktop app data (--include-desktop)" | |
| if $IS_MAC; then | |
| remove_path "$HOME/Library/Application Support/Claude" | |
| remove_path "$HOME/Library/Logs/Claude" | |
| remove_path "$HOME/Library/Saved Application State/com.anthropic.claudefordesktop.savedState" | |
| remove_path "$HOME/Library/Preferences/com.anthropic.claudefordesktop.plist" | |
| remove_path "$HOME/Library/Preferences/com.anthropic.claude.plist" | |
| fi | |
| if $IS_LINUX; then | |
| remove_path "$XDG_CONFIG/Claude" | |
| remove_path "$XDG_CONFIG/claude-desktop" | |
| fi | |
| else | |
| log "[3] Claude Desktop ${C_DIM}(skip — dùng --include-desktop)${C_RST}" | |
| fi | |
| # ============================================================ | |
| # Section 4 — Package managers (uninstall) + binary sweep | |
| # ============================================================ | |
| echo | |
| if $KEEP_PM; then | |
| log "[4a] PM uninstall ${C_DIM}(skip — --keep-pm)${C_RST}" | |
| else | |
| log "[4a] Package manager uninstall" | |
| if pm_has_claude npm; then | |
| run_cmd "npm uninstall -g @anthropic-ai/claude-code" \ | |
| npm uninstall -g @anthropic-ai/claude-code | |
| else | |
| dim " (skip) npm: không có @anthropic-ai/claude-code" | |
| fi | |
| if pm_has_claude bun; then | |
| run_cmd "bun remove -g @anthropic-ai/claude-code" \ | |
| bun remove -g @anthropic-ai/claude-code | |
| else | |
| dim " (skip) bun: không có @anthropic-ai/claude-code" | |
| fi | |
| if pm_has_claude pnpm; then | |
| run_cmd "pnpm remove -g @anthropic-ai/claude-code" \ | |
| pnpm remove -g @anthropic-ai/claude-code | |
| else | |
| dim " (skip) pnpm: không có @anthropic-ai/claude-code" | |
| fi | |
| if pm_has_claude yarn; then | |
| run_cmd "yarn global remove @anthropic-ai/claude-code" \ | |
| yarn global remove @anthropic-ai/claude-code | |
| else | |
| dim " (skip) yarn: không có @anthropic-ai/claude-code" | |
| fi | |
| fi | |
| echo | |
| log "[4b] Quét binary còn sót (mọi PM bin dir + version manager)" | |
| # Build danh sách bin dir động + tĩnh | |
| declare -a BIN_DIRS=( | |
| "$HOME/.local/bin" | |
| "$HOME/.npm-global/bin" | |
| "$HOME/.vite-plus/bin" | |
| "$HOME/.bun/bin" | |
| "$HOME/.deno/bin" | |
| "$HOME/.volta/bin" | |
| "/usr/local/bin" | |
| "/opt/homebrew/bin" | |
| ) | |
| # npm prefix động (nvm có thể đổi prefix theo node version) | |
| if command -v npm >/dev/null 2>&1; then | |
| np="$(npm prefix -g 2>/dev/null)" | |
| [[ -n "$np" ]] && BIN_DIRS+=( "$np/bin" ) | |
| fi | |
| # bun | |
| if command -v bun >/dev/null 2>&1; then | |
| bp="$(bun pm bin -g 2>/dev/null)" | |
| [[ -n "$bp" ]] && BIN_DIRS+=( "$bp" ) | |
| fi | |
| # pnpm | |
| if command -v pnpm >/dev/null 2>&1; then | |
| pp="$(pnpm bin -g 2>/dev/null)" | |
| [[ -n "$pp" ]] && BIN_DIRS+=( "$pp" ) | |
| fi | |
| # yarn | |
| if command -v yarn >/dev/null 2>&1; then | |
| yp="$(yarn global bin 2>/dev/null)" | |
| [[ -n "$yp" ]] && BIN_DIRS+=( "$yp" ) | |
| fi | |
| # nvm: mọi node version | |
| if [[ -d "$HOME/.nvm/versions/node" ]]; then | |
| while IFS= read -r -d '' nv; do | |
| BIN_DIRS+=( "$nv/bin" ) | |
| done < <(find "$HOME/.nvm/versions/node" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null) | |
| fi | |
| # fnm | |
| if [[ -d "$HOME/.fnm/node-versions" ]]; then | |
| while IFS= read -r -d '' nv; do | |
| BIN_DIRS+=( "$nv/installation/bin" ) | |
| done < <(find "$HOME/.fnm/node-versions" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null) | |
| fi | |
| # n (version manager: ~/n hoặc /usr/local/n) | |
| [[ -d "$HOME/n/bin" ]] && BIN_DIRS+=( "$HOME/n/bin" ) | |
| # Dedupe | |
| mapfile -t BIN_DIRS < <(printf '%s\n' "${BIN_DIRS[@]}" | awk '!seen[$0]++') | |
| for d in "${BIN_DIRS[@]}"; do | |
| for f in "$d/claude" "$d/claude-code"; do | |
| [[ -e "$f" || -L "$f" ]] && remove_path "$f" | |
| done | |
| done | |
| # ============================================================ | |
| # Section 5 — Shell completions (opt-in) | |
| # ============================================================ | |
| echo | |
| if $REMOVE_COMPLETIONS; then | |
| log "[5] Shell completions (--remove-completions)" | |
| remove_path "$HOME/.config/fish/completions/claude.fish" | |
| remove_path "$HOME/.config/fish/completions/claude-code.fish" | |
| remove_path "$HOME/.zsh/completions/_claude" | |
| remove_path "$HOME/.oh-my-zsh/completions/_claude" | |
| remove_path "$HOME/.bash_completion.d/claude" | |
| if $IS_MAC; then | |
| remove_path "/usr/local/share/zsh/site-functions/_claude" | |
| remove_path "/opt/homebrew/share/zsh/site-functions/_claude" | |
| fi | |
| else | |
| log "[5] Shell completions ${C_DIM}(skip — dùng --remove-completions)${C_RST}" | |
| fi | |
| # ============================================================ | |
| # Section 6 — macOS Keychain (opt-in) | |
| # ============================================================ | |
| echo | |
| if $PURGE_KEYCHAIN && $IS_MAC; then | |
| log "[6] macOS Keychain entries (--purge-keychain)" | |
| for svc in "Claude Code" "claude.ai" "Anthropic" "anthropic"; do | |
| if security find-generic-password -s "$svc" >/dev/null 2>&1; then | |
| run_cmd "security delete-generic-password -s '$svc'" \ | |
| security delete-generic-password -s "$svc" | |
| else | |
| dim " (skip) keychain service không tồn tại: $svc" | |
| fi | |
| done | |
| elif $PURGE_KEYCHAIN; then | |
| log "[6] Keychain ${C_DIM}(skip — không phải macOS)${C_RST}" | |
| else | |
| log "[6] Keychain ${C_DIM}(skip — dùng --purge-keychain trên macOS)${C_RST}" | |
| fi | |
| # ============================================================ | |
| # Section 7 — PATH cross-check | |
| # ============================================================ | |
| echo | |
| log "[7] PATH cross-check (\`which -a claude\`)" | |
| if command -v which >/dev/null 2>&1; then | |
| remaining=$(which -a claude 2>/dev/null || true) | |
| if [[ -z "$remaining" ]]; then | |
| ok " PATH sạch — không còn binary 'claude'" | |
| else | |
| if $DRY_RUN; then | |
| warn " Sau dry-run, các binary này vẫn sẽ còn trong PATH (kiểm tra lại sau --execute):" | |
| else | |
| warn " Vẫn còn binary 'claude' trong PATH — không tự động xoá (nằm ngoài whitelist):" | |
| fi | |
| while IFS= read -r line; do | |
| echo " - $line" | |
| done <<< "$remaining" | |
| dim " → Nếu cần, xoá thủ công: rm <path>" | |
| fi | |
| fi | |
| # ---------- Confirm & summary ---------- | |
| echo | |
| if $DRY_RUN; then | |
| echo "${C_YEL}=== DRY-RUN HOÀN TẤT — chạy lại với --execute để xoá thật ===${C_RST}" | |
| exit 0 | |
| fi | |
| if ! confirm; then | |
| warn "Đã huỷ. Không có gì bị xoá thêm sau confirm." | |
| exit 1 | |
| fi | |
| echo | |
| ok "Hoàn tất. Claude Code đã được gỡ." | |
| echo | |
| log "Kiểm tra sau gỡ:" | |
| dim " command -v claude # nên rỗng" | |
| dim " ls ~/.claude # nên 'No such file or directory'" | |
| dim " ls ~/.claude.json # nên 'No such file or directory'" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment