Working around Claude resilience issues
0
claude-json-watcher.md
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```