Skip to content

Instantly share code, notes, and snippets.

@rcalsaverini
Created January 30, 2026 16:45
Show Gist options
  • Select an option

  • Save rcalsaverini/9352416abcfd5519a1f9d86d711c60f3 to your computer and use it in GitHub Desktop.

Select an option

Save rcalsaverini/9352416abcfd5519a1f9d86d711c60f3 to your computer and use it in GitHub Desktop.
Uninstall clawdbot / moltbot / openclaw
#!/bin/bash
set -euo pipefail
# OpenClaw/Clawdbot/Moltbot/Moldbot Uninstaller for macOS and Linux
# Reverses everything done by the installer (handles all name variants)
BOLD='\033[1m'
ACCENT='\033[38;2;255;90;45m'
SUCCESS='\033[38;2;47;191;113m'
WARN='\033[38;2;255;176;32m'
ERROR='\033[38;2;226;61;45m'
INFO='\033[38;2;255;138;91m'
MUTED='\033[38;2;139;127;119m'
NC='\033[0m'
DRY_RUN=0
KEEP_CONFIG=0
REMOVE_NODE=0
SCAN_ONLY=0
HELP=0
# All known package/binary names
PACKAGE_NAMES=("openclaw" "clawdbot" "moltbot" "moldbot")
# All known config directory names
CONFIG_DIRS=(".openclaw" ".clawdbot" ".moltbot" ".moldbot")
# All known git checkout locations
GIT_DIRS=("openclaw" "clawdbot" "moltbot" "moldbot")
# Store our own PID to avoid killing ourselves
SELF_PID=$$
print_usage() {
cat <<EOF
OpenClaw/Clawdbot/Moltbot Uninstaller (macOS + Linux)
Handles all name variants: openclaw, clawdbot, moltbot, moldbot
Usage:
./uninstall-openclaw.sh [options]
Options:
--scan Scan and show what's installed without removing anything
--dry-run Show what would be removed without making changes
--keep-config Keep configuration files (~/.openclaw, ~/.clawdbot, etc.)
--remove-node Also remove Node.js (if installed by the installer)
--help, -h Show this help
Examples:
./uninstall-openclaw.sh --scan
./uninstall-openclaw.sh --dry-run
./uninstall-openclaw.sh
./uninstall-openclaw.sh --keep-config
EOF
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--scan)
SCAN_ONLY=1
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
--keep-config)
KEEP_CONFIG=1
shift
;;
--remove-node)
REMOVE_NODE=1
shift
;;
--help|-h)
HELP=1
shift
;;
*)
shift
;;
esac
done
}
log_action() {
if [[ "$DRY_RUN" == "1" ]]; then
echo -e "${MUTED}[DRY-RUN]${NC} $1"
else
echo -e "${WARN}→${NC} $1"
fi
}
log_success() {
echo -e "${SUCCESS}✓${NC} $1"
}
log_skip() {
echo -e "${MUTED}○${NC} $1"
}
log_found() {
echo -e "${INFO}●${NC} $1"
}
log_not_found() {
echo -e "${MUTED}○${NC} $1"
}
is_root() {
[[ "$(id -u)" -eq 0 ]]
}
maybe_sudo() {
if is_root; then
if [[ "${1:-}" == "-E" ]]; then
shift
fi
"$@"
else
sudo "$@"
fi
}
remove_file() {
local path="$1"
if [[ -e "$path" || -L "$path" ]]; then
log_action "Removing: ${INFO}${path}${NC}"
if [[ "$DRY_RUN" != "1" ]]; then
rm -f "$path"
fi
return 0
fi
return 1
}
remove_dir() {
local path="$1"
if [[ -d "$path" ]]; then
log_action "Removing directory: ${INFO}${path}${NC}"
if [[ "$DRY_RUN" != "1" ]]; then
rm -rf "$path"
fi
return 0
fi
return 1
}
remove_line_from_file() {
local file="$1"
local pattern="$2"
local description="$3"
if [[ -f "$file" ]] && grep -q "$pattern" "$file" 2>/dev/null; then
log_action "Removing ${description} from ${INFO}${file}${NC}"
if [[ "$DRY_RUN" != "1" ]]; then
# Create backup
cp "$file" "${file}.bak-uninstall"
grep -v "$pattern" "$file" > "${file}.tmp" || true
mv "${file}.tmp" "$file"
fi
return 0
fi
return 1
}
# ============================================================================
# SCAN FUNCTIONS - Show what's installed
# ============================================================================
scan_installation() {
echo -e "${ACCENT}${BOLD}"
echo " 🔍 Scanning for OpenClaw/Clawdbot/Moltbot installation..."
echo -e "${NC}"
echo -e "${MUTED}Looking for: ${PACKAGE_NAMES[*]}${NC}"
echo ""
local found_anything=0
# --- npm packages ---
echo -e "${BOLD}npm global packages:${NC}"
for name in "${PACKAGE_NAMES[@]}"; do
local version=""
version=$(npm list -g "$name" 2>/dev/null | grep "$name@" | head -1 || true)
if [[ -n "$version" ]]; then
log_found "$version"
found_anything=1
fi
done
if [[ "$found_anything" == "0" ]]; then
log_not_found "No npm packages found"
fi
echo ""
# --- npm global location ---
echo -e "${BOLD}npm global install location:${NC}"
local npm_root=""
npm_root="$(npm root -g 2>/dev/null || true)"
if [[ -n "$npm_root" ]]; then
for name in "${PACKAGE_NAMES[@]}"; do
if [[ -d "$npm_root/$name" ]]; then
log_found "$npm_root/$name"
fi
done
fi
local npm_bin=""
npm_bin="$(npm bin -g 2>/dev/null || true)"
if [[ -n "$npm_bin" ]]; then
for name in "${PACKAGE_NAMES[@]}"; do
if [[ -e "$npm_bin/$name" ]]; then
log_found "$npm_bin/$name"
fi
done
fi
echo ""
# --- Config directories ---
echo -e "${BOLD}Configuration directories:${NC}"
local found_config=0
for dir in "${CONFIG_DIRS[@]}"; do
if [[ -d "$HOME/$dir" ]]; then
log_found "$HOME/$dir"
ls -la "$HOME/$dir" 2>/dev/null | head -10 | while read -r line; do
echo -e " ${MUTED}$line${NC}"
done
found_config=1
fi
done
if [[ "$found_config" == "0" ]]; then
log_not_found "No config directories found"
fi
echo ""
# --- Git checkouts ---
echo -e "${BOLD}Git checkouts:${NC}"
local found_git=0
for name in "${GIT_DIRS[@]}"; do
if [[ -d "$HOME/$name" && -f "$HOME/$name/package.json" ]]; then
if grep -q '"name".*"openclaw\|clawdbot\|moltbot\|moldbot"' "$HOME/$name/package.json" 2>/dev/null; then
log_found "$HOME/$name"
found_git=1
fi
fi
done
if [[ "$found_git" == "0" ]]; then
log_not_found "No git checkouts found"
fi
echo ""
# --- Wrapper scripts ---
echo -e "${BOLD}Wrapper scripts in ~/.local/bin:${NC}"
local found_wrapper=0
for name in "${PACKAGE_NAMES[@]}"; do
if [[ -e "$HOME/.local/bin/$name" ]]; then
log_found "$HOME/.local/bin/$name"
found_wrapper=1
fi
done
if [[ "$found_wrapper" == "0" ]]; then
log_not_found "No wrapper scripts found"
fi
echo ""
# --- systemd services ---
echo -e "${BOLD}systemd user services:${NC}"
local systemd_dir="$HOME/.config/systemd/user"
local found_systemd=0
if [[ -d "$systemd_dir" ]]; then
for name in "${PACKAGE_NAMES[@]}"; do
for service in "$systemd_dir/${name}"*.service "$systemd_dir/${name}"*.timer; do
if [[ -f "$service" ]]; then
local status=""
status=$(systemctl --user is-active "$(basename "$service")" 2>/dev/null || echo "inactive")
log_found "$service (${status})"
found_systemd=1
fi
done
done
fi
if [[ "$found_systemd" == "0" ]]; then
log_not_found "No systemd services found"
fi
echo ""
# --- Running processes ---
echo -e "${BOLD}Running processes:${NC}"
local found_proc=0
for name in "${PACKAGE_NAMES[@]}"; do
local procs=""
# Exclude uninstall script from matches
procs=$(pgrep -fa "$name" 2>/dev/null | grep -v "uninstall" | grep -v "grep" || true)
if [[ -n "$procs" ]]; then
echo "$procs" | while read -r line; do
log_found "$line"
done
found_proc=1
fi
done
if [[ "$found_proc" == "0" ]]; then
log_not_found "No running processes found"
fi
echo ""
# --- Shell RC files ---
echo -e "${BOLD}Shell RC files with PATH modifications:${NC}"
local found_rc=0
for rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
if [[ -f "$rc" ]] && grep -q "openclaw\|clawdbot\|moltbot\|moldbot\|npm-global" "$rc" 2>/dev/null; then
log_found "$rc"
grep "openclaw\|clawdbot\|moltbot\|moldbot\|npm-global" "$rc" 2>/dev/null | while read -r line; do
echo -e " ${MUTED}$line${NC}"
done
found_rc=1
fi
done
if [[ "$found_rc" == "0" ]]; then
log_not_found "No PATH modifications found"
fi
echo ""
# --- Symlinks in bin directories ---
echo -e "${BOLD}Symlinks in system bin directories:${NC}"
local found_symlink=0
for bin_dir in "/usr/local/bin" "/opt/homebrew/bin"; do
for name in "${PACKAGE_NAMES[@]}"; do
if [[ -e "$bin_dir/$name" ]]; then
local target=""
target=$(readlink "$bin_dir/$name" 2>/dev/null || echo "(not a symlink)")
log_found "$bin_dir/$name -> $target"
found_symlink=1
fi
done
done
if [[ "$found_symlink" == "0" ]]; then
log_not_found "No symlinks found"
fi
echo ""
# --- Deep find ---
echo -e "${BOLD}All related files (find):${NC}"
find "$HOME" -maxdepth 4 \( -name "*openclaw*" -o -name "*clawdbot*" -o -name "*moltbot*" -o -name "*moldbot*" \) 2>/dev/null | grep -v "uninstall" | head -30 | while read -r f; do
log_found "$f"
done
echo ""
echo -e "${SUCCESS}${BOLD}Scan complete${NC}"
echo -e "${MUTED}Run with --dry-run to see what would be removed, or without flags to uninstall.${NC}"
}
# ============================================================================
# UNINSTALL FUNCTIONS
# ============================================================================
# Stop daemon if running (using systemctl and pkill directly, not clawdbot command)
stop_daemon() {
local found=0
local systemd_user_dir="$HOME/.config/systemd/user"
# First, stop and disable any systemd services
if [[ -d "$systemd_user_dir" ]]; then
for name in "${PACKAGE_NAMES[@]}"; do
for service in "$systemd_user_dir/${name}"*.service; do
if [[ -f "$service" ]]; then
local service_name
service_name="$(basename "$service")"
log_action "Stopping and disabling systemd service: ${INFO}${service_name}${NC}"
if [[ "$DRY_RUN" != "1" ]]; then
# Use timeout to prevent hanging
timeout 5 systemctl --user disable --now "$service_name" 2>/dev/null || true
fi
found=1
fi
done
done
fi
# Kill any remaining processes by name (EXCLUDING this script!)
for name in "${PACKAGE_NAMES[@]}"; do
# Get PIDs matching the name, but exclude our own script
local pids=""
pids=$(pgrep -f "$name" 2>/dev/null | grep -v "^${SELF_PID}$" || true)
# Also filter out any process that contains "uninstall" in its command line
if [[ -n "$pids" ]]; then
for pid in $pids; do
local cmdline=""
cmdline=$(ps -p "$pid" -o args= 2>/dev/null || true)
if [[ -n "$cmdline" && ! "$cmdline" =~ uninstall ]]; then
log_action "Killing process $pid: ${INFO}${cmdline}${NC}"
if [[ "$DRY_RUN" != "1" ]]; then
kill "$pid" 2>/dev/null || true
fi
found=1
fi
done
# Wait and force kill if needed
if [[ "$DRY_RUN" != "1" ]]; then
sleep 1
for pid in $pids; do
local cmdline=""
cmdline=$(ps -p "$pid" -o args= 2>/dev/null || true)
if [[ -n "$cmdline" && ! "$cmdline" =~ uninstall ]]; then
kill -9 "$pid" 2>/dev/null || true
fi
done
fi
fi
done
if [[ "$found" == "1" ]]; then
log_success "Daemon stopped"
else
log_skip "No running daemon found"
fi
}
# Remove systemd user service files (already stopped/disabled by stop_daemon)
remove_systemd_services() {
local systemd_user_dir="$HOME/.config/systemd/user"
local found=0
if [[ -d "$systemd_user_dir" ]]; then
for name in "${PACKAGE_NAMES[@]}"; do
for service in "$systemd_user_dir/${name}"*.service "$systemd_user_dir/${name}"*.timer; do
if [[ -f "$service" ]]; then
if remove_file "$service"; then
found=1
fi
fi
done
done
# Reload systemd after removing services
if [[ "$found" == "1" && "$DRY_RUN" != "1" ]]; then
systemctl --user daemon-reload 2>/dev/null || true
fi
fi
if [[ "$found" == "1" ]]; then
log_success "systemd service files removed"
else
log_skip "No systemd user services found"
fi
}
# Remove npm global packages (all variants)
remove_npm_packages() {
local found=0
for name in "${PACKAGE_NAMES[@]}"; do
if npm list -g "$name" &>/dev/null 2>&1; then
log_action "Uninstalling npm global package: ${INFO}${name}${NC}"
if [[ "$DRY_RUN" != "1" ]]; then
npm uninstall -g "$name" 2>/dev/null || true
fi
found=1
fi
done
if [[ "$found" == "1" ]]; then
log_success "npm package(s) removed"
else
log_skip "No npm packages installed (checked: ${PACKAGE_NAMES[*]})"
fi
# Clean up any leftover npm paths
local npm_root=""
npm_root="$(npm root -g 2>/dev/null || true)"
if [[ -n "$npm_root" && -d "$npm_root" ]]; then
for name in "${PACKAGE_NAMES[@]}"; do
for leftover in "$npm_root"/."${name}"-* "$npm_root"/"$name"; do
if [[ -e "$leftover" ]]; then
remove_dir "$leftover"
fi
done
done
fi
}
# Remove git checkout wrapper and source (all variants)
remove_git_install() {
local found_wrapper=0
local found_checkout=0
# Remove wrapper scripts from ~/.local/bin
for name in "${PACKAGE_NAMES[@]}"; do
if remove_file "$HOME/.local/bin/$name"; then
found_wrapper=1
fi
done
if [[ "$found_wrapper" == "1" ]]; then
log_success "Git wrapper(s) removed"
fi
# Remove git checkouts from home directory
for name in "${GIT_DIRS[@]}"; do
local checkout_dir="$HOME/$name"
if [[ -d "$checkout_dir" ]]; then
# Verify it's actually the bot checkout
if [[ -f "$checkout_dir/package.json" ]]; then
local is_match=0
for pkg_name in "${PACKAGE_NAMES[@]}"; do
if grep -q "\"name\".*\"$pkg_name\"" "$checkout_dir/package.json" 2>/dev/null; then
is_match=1
break
fi
done
if [[ "$is_match" == "1" ]]; then
log_action "Removing git checkout: ${INFO}${checkout_dir}${NC}"
if [[ "$DRY_RUN" != "1" ]]; then
rm -rf "$checkout_dir"
fi
found_checkout=1
fi
fi
fi
done
if [[ "$found_checkout" == "1" ]]; then
log_success "Git checkout(s) removed"
else
log_skip "No git checkouts found"
fi
}
# Remove configuration directories (all variants)
remove_config() {
if [[ "$KEEP_CONFIG" == "1" ]]; then
log_skip "Keeping configuration files (--keep-config specified)"
return
fi
local found=0
for dir_name in "${CONFIG_DIRS[@]}"; do
local full_path="$HOME/$dir_name"
if remove_dir "$full_path"; then
found=1
fi
done
if [[ "$found" == "1" ]]; then
log_success "Configuration directories removed"
else
log_skip "No configuration directories found"
fi
}
# Remove PATH modifications from shell rc files
clean_shell_rc() {
local modified=0
for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
if [[ -f "$rc" ]]; then
# Remove .npm-global/bin PATH addition
if remove_line_from_file "$rc" '.npm-global' "npm-global PATH"; then
modified=1
fi
# Remove .local/bin PATH additions with any of the bot names
for name in "${PACKAGE_NAMES[@]}"; do
if grep -q ".local/bin.*# ${name}\|# Added by ${name}" "$rc" 2>/dev/null; then
remove_line_from_file "$rc" "# ${name}\|# Added by ${name}" "${name} PATH entries"
modified=1
fi
done
fi
done
if [[ "$modified" == "1" ]]; then
log_success "Shell rc files cleaned"
echo -e "${INFO}i${NC} Backups created with .bak-uninstall suffix"
else
log_skip "No PATH modifications found in shell rc files"
fi
}
# Remove npm prefix configuration if set to ~/.npm-global
clean_npm_config() {
local npm_prefix=""
npm_prefix="$(npm config get prefix 2>/dev/null || true)"
if [[ "$npm_prefix" == "$HOME/.npm-global" ]]; then
log_action "Resetting npm prefix configuration"
if [[ "$DRY_RUN" != "1" ]]; then
npm config delete prefix 2>/dev/null || true
fi
log_success "npm prefix reset"
# Optionally remove the .npm-global directory if empty
if [[ -d "$HOME/.npm-global" ]]; then
if [[ -z "$(ls -A "$HOME/.npm-global" 2>/dev/null)" ]]; then
remove_dir "$HOME/.npm-global"
else
log_skip "~/.npm-global not empty, keeping it"
fi
fi
else
log_skip "npm prefix not set to ~/.npm-global"
fi
}
# Remove pnpm if it was installed
remove_pnpm() {
if command -v pnpm &> /dev/null; then
log_action "Removing pnpm..."
if [[ "$DRY_RUN" != "1" ]]; then
npm uninstall -g pnpm 2>/dev/null || true
# Also try corepack
if command -v corepack &> /dev/null; then
corepack disable pnpm 2>/dev/null || true
fi
fi
log_success "pnpm removed"
else
log_skip "pnpm not installed"
fi
}
# Remove stale symlinks from common bin directories
clean_stale_symlinks() {
local bin_dirs=(
"/usr/local/bin"
"/opt/homebrew/bin"
"$HOME/.local/bin"
)
local found=0
for bin_dir in "${bin_dirs[@]}"; do
if [[ -d "$bin_dir" ]]; then
for name in "${PACKAGE_NAMES[@]}"; do
local symlink="$bin_dir/$name"
if [[ -L "$symlink" ]]; then
local target=""
target="$(readlink "$symlink" 2>/dev/null || true)"
# Check if it points to node_modules for any of our packages
for pkg in "${PACKAGE_NAMES[@]}"; do
if [[ "$target" == *"/node_modules/${pkg}/"* ]]; then
log_action "Removing stale symlink: ${INFO}${symlink}${NC}"
if [[ "$DRY_RUN" != "1" ]]; then
rm -f "$symlink" 2>/dev/null || maybe_sudo rm -f "$symlink" 2>/dev/null || true
fi
found=1
break
fi
done
fi
done
fi
done
if [[ "$found" == "1" ]]; then
log_success "Stale symlinks removed"
fi
}
# Optionally remove Node.js
remove_node() {
if [[ "$REMOVE_NODE" != "1" ]]; then
return
fi
echo ""
echo -e "${WARN}WARNING: Removing Node.js${NC}"
echo "This will remove Node.js system-wide. Other applications may depend on it."
echo ""
local os="unknown"
if [[ "$OSTYPE" == "darwin"* ]]; then
os="macos"
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
os="linux"
fi
if [[ "$os" == "macos" ]]; then
if command -v brew &> /dev/null; then
log_action "Removing Node.js via Homebrew..."
if [[ "$DRY_RUN" != "1" ]]; then
brew uninstall node@22 2>/dev/null || brew uninstall node 2>/dev/null || true
fi
log_success "Node.js removed"
fi
elif [[ "$os" == "linux" ]]; then
log_action "Removing Node.js..."
if [[ "$DRY_RUN" != "1" ]]; then
if command -v apt-get &> /dev/null; then
maybe_sudo apt-get remove -y nodejs 2>/dev/null || true
elif command -v dnf &> /dev/null; then
maybe_sudo dnf remove -y nodejs 2>/dev/null || true
elif command -v yum &> /dev/null; then
maybe_sudo yum remove -y nodejs 2>/dev/null || true
fi
fi
log_success "Node.js removed"
fi
}
main() {
echo -e "${ACCENT}${BOLD}"
echo " 🦞 OpenClaw/Clawdbot/Moltbot Uninstaller"
echo -e "${NC}${MUTED} Goodbye, and thanks for all the fish.${NC}"
echo ""
echo -e "${MUTED} Checking for: ${PACKAGE_NAMES[*]}${NC}"
echo ""
if [[ "$DRY_RUN" == "1" ]]; then
echo -e "${WARN}Running in dry-run mode - no changes will be made${NC}"
echo ""
fi
# Stop daemon first
stop_daemon
# Remove systemd services
remove_systemd_services
# Remove npm packages
remove_npm_packages
# Remove git installations
remove_git_install
# Clean stale symlinks
clean_stale_symlinks
# Remove pnpm
remove_pnpm
# Clean npm config
clean_npm_config
# Clean shell rc files
clean_shell_rc
# Remove config directories
remove_config
# Optionally remove Node.js
remove_node
echo ""
if [[ "$DRY_RUN" == "1" ]]; then
echo -e "${SUCCESS}${BOLD}Dry run complete${NC}"
echo -e "${MUTED}Run without --dry-run to apply changes${NC}"
else
echo -e "${SUCCESS}${BOLD}🦞 Uninstall complete${NC}"
echo ""
echo -e "${MUTED}You may need to restart your terminal or run:${NC}"
echo -e " ${INFO}source ~/.bashrc${NC} ${MUTED}or${NC} ${INFO}source ~/.zshrc${NC}"
if [[ "$KEEP_CONFIG" == "1" ]]; then
echo ""
echo -e "${MUTED}Configuration files were kept. Remove manually if desired:${NC}"
echo -e " ${INFO}rm -rf ~/.openclaw ~/.clawdbot ~/.moltbot ~/.moldbot${NC}"
fi
fi
}
parse_args "$@"
if [[ "$HELP" == "1" ]]; then
print_usage
exit 0
fi
if [[ "$SCAN_ONLY" == "1" ]]; then
scan_installation
exit 0
fi
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment