🔧 The Problem
You've been happily using Claude Code. You've approved hundreds of permissions. Git commits, file reads, npm installs, SSH commands...
Then one day, it breaks. Maybe you approved a pattern with a heredoc. Maybe there's an emoji in there somewhere. Maybe Claude got a little too creative with a shell construct.
Your settings.local.json is now 847KB of chaos.
Claude refuses to start. You stare into the abyss.
"Have you tried turning it off and on again?"
— Everyone, before discovering this script
The Solution
An interactive, escalating cleaning tool. Start gentle. If that doesn't work, escalate. Keep escalating until either Claude works or nothing remains.
1#!/bin/bash 2# Fix broken Claude Code settings 3# Interactive progressive cleaning - escalates until fixed 4# Shows what will be lost at each level 5 6SETTINGS_FILE="$HOME/.claude/settings.local.json" 7BACKUP_DIR="$HOME/.claude/backups" 8 9# Colors - escalating severity 10GREEN='\033[0;32m' 11YELLOW='\033[1;33m' 12ORANGE='\033[0;33m' 13RED='\033[0;31m' 14BRIGHT_RED='\033[1;31m'
🌱 Level 1: Gentle Clean
Low ImpactThe diplomatic approach. We politely ask the bad patterns to leave. No hard feelings.
What gets removed:
Bash(done)— How did this even get here?Bash(for ...)— Shell constructs that snuck inBash(while ...)— Infinite loops of regretBash(if ...)— Conditional chaos- Patterns with
<<orEOF— Heredoc horrors - Patterns with
\n— Newlines don't belong here - Patterns with
()— Empty parentheses of doom - Patterns with
""— Double-double quote catastrophes
"We come in peace. We just want to remove the garbage."
You'll keep:
All your legitimate permissions. Git commands, file paths, web fetches — all safe.
231clean_level1() { 232 jq ' 233 .permissions.allow = ([ 234 .permissions.allow[] | 235 select( 236 (test("^Bash\\(done\\)") | not) and 237 (test("^Bash\\(for ") | not) and 238 (test("^Bash\\(while ") | not) and 239 (test("^Bash\\(if ") | not) and 240 (test("^Bash\\(source ") | not) and 241 (test("<<") | not) and 242 (test("EOF") | not) and 243 (test("\\\\n") | not) and 244 (test("\\(\\)") | not) and 245 (test("\"\"") | not) 246 ) 247 ] | unique) 248 ' "$SETTINGS_FILE" 249}
🔨 Level 2: Medium Clean
Medium ImpactGentle didn't work? Time to bring out the character counter. If your permission pattern is longer than a tweet, it's probably wrong.
Additional removals:
- Any pattern over 150 characters
- Patterns containing emojis like 🤖 😇 ✓ ✗
Those 500-character git commit messages you approved? Gone. That curl command with the full JSON payload? Sayonara.
Trade-off:
You'll need to re-approve some complex git commits. A small price for sanity.
"If it doesn't fit in a tweet, it doesn't fit in your permissions."
251clean_level2() { 252 jq ' 253 .permissions.allow = ([ 254 .permissions.allow[] | 255 select( 256 (length < 150) and 257 (test("^Bash\\(done\\)") | not) and 258 (test("^Bash\\(for ") | not) and 259 (test("^Bash\\(while ") | not) and 260 (test("^Bash\\(if ") | not) and 261 (test("^Bash\\(source ") | not) and 262 (test("<<") | not) and 263 (test("EOF") | not) and 264 (test("\\\\n") | not) and 265 (test("\\(\\)") | not) and 266 (test("\"\"") | not) and 267 (test("🤖|😇|✓|✗") | not) 268 ) 269 ] | unique) 270 ' "$SETTINGS_FILE" 271}
⚡ Level 3: Hard Clean
High ImpactNow we're getting serious. We're keeping only the patterns that look like they were written by a reasonable human.
Survival criteria:
- Under 100 characters
- Ends with
:*)(wildcard patterns) - OR is a simple tool pattern like
Bash(git add:*)
What you'll lose:
Specific file paths, exact curl commands, that one SSH command you approved at 2am. All gone.
What survives:
Bash(git:*), WebFetch, Read —
the classics. The survivors.
"We're not angry. We're just... disappointed in your permissions."
273clean_level3() { 274 jq ' 275 .permissions.allow = ([ 276 .permissions.allow[] | 277 select( 278 (length < 100) and 279 (test(":\\*\\)$") or 280 test("^(Bash|Read|WebFetch|WebSearch|Skill)\\([^)]+\\)$")) and 281 (test("[\\n\\r\\t]") | not) and 282 (test("<<|EOF") | not) and 283 (test("\\(\\)") | not) 284 ) 285 ] | unique) 286 ' "$SETTINGS_FILE" 287}
☢️ Level 4: NUCLEAR
Total ResetStill broken? Fine. We'll just delete all your permissions. Every. Single. One.
⚠️ You will need to re-approve EVERYTHING:
- Every git command
- Every file read outside cwd
- Every web fetch
- Every npm/uv/cargo command
- Every ssh/curl command
Your settings.local.json will be pristine.
A blank canvas. Tabula rasa.
"I am become Death, the destroyer of permissions."
Silver lining:
Your MCP servers, hooks, and plugins are still safe.
Those live in settings.json, not settings.local.json.
288clean_level4() { 289 jq '.permissions = {"allow": [], "deny": [], "ask": []}' \ 290 "$SETTINGS_FILE" 291} 292 293# Usage in interactive mode: 294 459# Level 4 - Red (nuclear) 460echo -e "${RED}╔══════════════════════════════════════╗${NC}" 461echo -e "${RED}║ Level 4: NUCLEAR ║${NC}" 462echo -e "${RED}╚══════════════════════════════════════╝${NC}" 463preview_removals 4 464apply_level 4 465echo -e "${RED}✓ All permissions reset.${NC}"
💥 Level 5: DOUBLE NUKE
Settings Extinction
Nuclear wasn't enough? We're going deeper.
Time to reset settings.json itself.
⚠️ YOU WILL LOSE:
- All MCP server configurations
- All hooks
- Plugin enable/disable states
That beautiful MCP setup you spent 3 hours configuring? Hope you documented it somewhere.
What survives:
Conversation history. Authentication. Your memories of better times.
"The first nuke was a warning. The second is a statement."
292clean_level5() { 293 # Double nuke - reset settings.json to minimal 294 local main_settings="$HOME/.claude/settings.json" 295 296 echo -e "${BRIGHT_RED}Backing up settings.json...${NC}" 297 mkdir -p "$BACKUP_DIR" 298 cp "$main_settings" \ 299 "$BACKUP_DIR/settings.$(date +%Y%m%d_%H%M%S).json" 300 301 echo -e "${BRIGHT_RED}Resetting to minimal defaults...${NC}" 302 echo '{"$schema": "https://..."}' > "$main_settings" 303 304 echo -e "${BRIGHT_RED}Done. MCP, hooks, plugins cleared.${NC}" 305}
🧠 Level 6: FORGET
Memory WipeSettings are fine but Claude still won't work? Maybe it's the memories that are cursed.
⚠️⚠️⚠️ THIS CANNOT BE UNDONE ⚠️⚠️⚠️
All conversation history in ~/.claude/projects/
will be permanently deleted.
What you'll lose:
- Every past conversation
- All session context
- That one time Claude helped you fix a production bug at 3am
- The inside jokes. The memories. All of it.
What survives:
Authentication stays intact. You won't need to re-login. Small mercies.
"Eternal Sunshine of the Spotless Claude."
303clean_level6() { 304 # Forget - clear conversation history 305 # ⚠️ IRREVERSIBLE! 306 307 echo -e "${BRIGHT_RED}WARNING: CANNOT BE UNDONE!${NC}" 308 echo -e "${BRIGHT_RED}Clearing conversation history...${NC}" 309 310 rm -rf "$HOME/.claude/projects" 311 312 echo -e "${BRIGHT_RED}Done. Past permanently forgotten.${NC}" 313} 314 315# Preview shows the gravity of this decision: 174local history_count=$(find "$HOME/.claude/projects" \ 175 -name "*.jsonl" 2>/dev/null | wc -l) 176echo "$history_count session files will be DESTROYED"
🔥 Level 7: SCORCHED EARTH
Complete Destruction
This is it. The point of no return.
We're deleting the entire ~/.claude directory.
🔥🔥🔥 EVERYTHING WILL BE DESTROYED 🔥🔥🔥
- All permissions
- All MCP server configs
- All hooks
- All plugin configs
- Session history
- Authentication tokens — You WILL need to re-login
After this, you'll need to:
- Run
claudeand log in again - Re-configure all MCP servers
- Re-enable plugins
- Re-approve all permissions as you work
- Rebuild your life from the ashes
Silver lining:
A full backup is created at ~/.claude-backup-* first.
We're destructive, not cruel.
"From the ashes, a new Claude shall rise."
311clean_level7() { 312 # Scorched earth - backup then nuke ~/.claude 313 314 local full_backup_dir=\ 315 "$HOME/.claude-backup-$(date +%Y%m%d_%H%M%S)" 316 317 echo -e "${BRIGHT_RED}Creating backup at $full_backup_dir${NC}" 318 cp -r "$HOME/.claude" "$full_backup_dir" 319 320 echo -e "${BRIGHT_RED}Destroying ~/.claude...${NC}" 321 rm -rf "$HOME/.claude" 322 323 echo -e "${BRIGHT_RED}Done. Re-login required.${NC}" 324}
💀 Level 8: OVERKILL
Total AnnihilationScorched Earth wasn't enough? You absolute madman. Fine. We'll delete everything.
💀💀💀 TOTAL ANNIHILATION 💀💀💀
~/.claude— Gone~/CLAUDE.md— Gone~/.claude-md-history— Gone- Your dignity — Questionable
Claude will be as if you never used it before. Complete blank slate. Factory reset. Year zero.
The aftermath:
- Run
claudeand log in again - Re-configure EVERYTHING from scratch
- Recreate
~/CLAUDE.md - Contemplate your life choices
- Start a meditation practice
"In my end is my beginning."
— T.S. Eliot, probably not about Claude
321clean_level8() { 322 # OVERKILL - nuke everything including CLAUDE.md 323 local timestamp=$(date +%Y%m%d_%H%M%S) 324 325 # Backup ~/.claude 326 local full_backup_dir="$HOME/.claude-backup-$timestamp" 327 cp -r "$HOME/.claude" "$full_backup_dir" 328 329 # Backup ~/CLAUDE.md 330 if [[ -f "$CLAUDE_MD" ]]; then 331 cp "$CLAUDE_MD" "$HOME/.claude-md-backup-$timestamp" 332 fi 333 334 # DESTROY EVERYTHING 335 rm -rf "$HOME/.claude" 336 rm -f "$CLAUDE_MD" 337 rm -rf "$CLAUDE_MD_REPO" 338 339 echo -e "${BRIGHT_RED}TOTAL ANNIHILATION COMPLETE.${NC}" 340}
📝 Bonus: CLAUDE.md Management
Version Control for Your Instructions
Ever edit your ~/CLAUDE.md and immediately regret it?
This script also includes a git-based snapshot system.
Available commands:
./script instructions snapshot [name]— Save current state./script instructions list— Show all snapshots./script instructions diff [snapshot]— Compare versions./script instructions restore [snap]— Go back in time./script instructions update <file>— Replace from file
All snapshots are stored in ~/.claude-md-history as a git repo.
"Those who cannot remember the past are condemned to repeat their prompts."
560init_instructions_repo() { 561 if [[ ! -d "$CLAUDE_MD_REPO" ]]; then 562 mkdir -p "$CLAUDE_MD_REPO" 563 git -C "$CLAUDE_MD_REPO" init --quiet 564 565 if [[ -f "$CLAUDE_MD" ]]; then 566 cp "$CLAUDE_MD" "$CLAUDE_MD_REPO/CLAUDE.md" 567 git -C "$CLAUDE_MD_REPO" add CLAUDE.md 568 git -C "$CLAUDE_MD_REPO" commit \ 569 -m "Initial snapshot" --quiet 570 fi 571 fi 572} 573 574instructions_snapshot() { 575 local name="${1:-snapshot-$(date +%Y%m%d_%H%M%S)}" 576 init_instructions_repo 577 578 cp "$CLAUDE_MD" "$CLAUDE_MD_REPO/CLAUDE.md" 579 git -C "$CLAUDE_MD_REPO" add CLAUDE.md 580 git -C "$CLAUDE_MD_REPO" commit -m "$name" --quiet 581 git -C "$CLAUDE_MD_REPO" tag "$name" 582}