Working around Claude resilience issues
0
claude-json-watcher.md
104 lines 2.8 kB view raw view rendered
1# `.claude.json` settings keep getting wiped 2 3Claude Code intermittently erases configurations from `~/.claude.json` — no error, no warning, settings just gone. Set up a launchd file watcher to catch it in the act. 4 5## The watcher 6 7`~/Library/LaunchAgents/com.user.watch-claude-json.plist` — copies a timestamped snapshot into `~/.claude/backups/` on every write to the file. 8 9```xml 10<?xml version="1.0" encoding="UTF-8"?> 11<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 12<plist version="1.0"> 13<dict> 14 <key>Label</key> 15 <string>com.user.watch-claude-json</string> 16 <key>ProgramArguments</key> 17 <array> 18 <string>/bin/bash</string> 19 <string>-c</string> 20 <string>cp ~/.claude.json ~/.claude/backups/claude-json-$(date +%Y%m%d-%H%M%S).json</string> 21 </array> 22 <key>WatchPaths</key> 23 <array> 24 <string>/Users/user/.claude.json</string> 25 </array> 26</dict> 27</plist> 28``` 29 30## Scripts 31 32**`diff-backups.sh`** — diffs consecutive snapshots and pipes into `diffnav`: 33 34```bash 35#!/usr/bin/env bash 36set -euo pipefail 37 38BACKUP_DIR="${1:-$(dirname "$0")}" 39 40mapfile -t files < <(ls -1 "$BACKUP_DIR"/claude-json-*.json 2>/dev/null | sort) 41 42if [[ ${#files[@]} -lt 2 ]]; then 43 echo "Need at least 2 backup files to diff." 44 exit 1 45fi 46 47combined="" 48for ((i = 0; i < ${#files[@]} - 1; i++)); do 49 older="${files[$i]}" 50 newer="${files[$i + 1]}" 51 older_name=$(basename "$older") 52 newer_name=$(basename "$newer") 53 chunk=$(diff -u --label "a/${older_name}" --label "b/${newer_name}" "$older" "$newer" 2>/dev/null || true) 54 if [[ -n "$chunk" ]]; then 55 combined+="${chunk} 56" 57 fi 58done 59 60if [[ -z "$combined" ]]; then 61 echo "No differences found between any consecutive backups." 62 exit 0 63fi 64 65echo "$combined" | diffnav 66``` 67 68**`prune-backups.sh`** — removes snapshots identical to the previous one (`DRY_RUN=true` to preview): 69 70```bash 71#!/usr/bin/env bash 72set -euo pipefail 73 74BACKUP_DIR="${1:-$(dirname "$0")}" 75DRY_RUN="${DRY_RUN:-false}" 76 77mapfile -t files < <(ls -1 "$BACKUP_DIR"/claude-json-*.json 2>/dev/null | sort) 78 79if [[ ${#files[@]} -lt 2 ]]; then 80 echo "Need at least 2 backup files to compare." 81 exit 0 82fi 83 84removed=0 85for ((i = 1; i < ${#files[@]}; i++)); do 86 prev="${files[$i - 1]}" 87 curr="${files[$i]}" 88 if cmp -s "$prev" "$curr"; then 89 if [[ "$DRY_RUN" == "true" ]]; then 90 echo "[dry-run] would remove $(basename "$curr") (same as $(basename "$prev"))" 91 else 92 echo "Removing $(basename "$curr") (same as $(basename "$prev"))" 93 rm "$curr" 94 fi 95 removed=$((removed + 1)) 96 fi 97done 98 99if [[ "$DRY_RUN" == "true" ]]; then 100 echo "Done. ${removed} duplicate(s) would be removed." 101else 102 echo "Done. ${removed} duplicate(s) removed." 103fi 104```