Working around Claude resilience issues
0
claude-json-watcher.md
104 lines 2.8 kB view raw view code

.claude.json settings keep getting wiped#

Claude 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.

The watcher#

~/Library/LaunchAgents/com.user.watch-claude-json.plist — copies a timestamped snapshot into ~/.claude/backups/ on every write to the file.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.user.watch-claude-json</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>-c</string>
        <string>cp ~/.claude.json ~/.claude/backups/claude-json-$(date +%Y%m%d-%H%M%S).json</string>
    </array>
    <key>WatchPaths</key>
    <array>
        <string>/Users/user/.claude.json</string>
    </array>
</dict>
</plist>

Scripts#

diff-backups.sh — diffs consecutive snapshots and pipes into diffnav:

#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR="${1:-$(dirname "$0")}"

mapfile -t files < <(ls -1 "$BACKUP_DIR"/claude-json-*.json 2>/dev/null | sort)

if [[ ${#files[@]} -lt 2 ]]; then
  echo "Need at least 2 backup files to diff."
  exit 1
fi

combined=""
for ((i = 0; i < ${#files[@]} - 1; i++)); do
  older="${files[$i]}"
  newer="${files[$i + 1]}"
  older_name=$(basename "$older")
  newer_name=$(basename "$newer")
  chunk=$(diff -u --label "a/${older_name}" --label "b/${newer_name}" "$older" "$newer" 2>/dev/null || true)
  if [[ -n "$chunk" ]]; then
    combined+="${chunk}
"
  fi
done

if [[ -z "$combined" ]]; then
  echo "No differences found between any consecutive backups."
  exit 0
fi

echo "$combined" | diffnav

prune-backups.sh — removes snapshots identical to the previous one (DRY_RUN=true to preview):

#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR="${1:-$(dirname "$0")}"
DRY_RUN="${DRY_RUN:-false}"

mapfile -t files < <(ls -1 "$BACKUP_DIR"/claude-json-*.json 2>/dev/null | sort)

if [[ ${#files[@]} -lt 2 ]]; then
  echo "Need at least 2 backup files to compare."
  exit 0
fi

removed=0
for ((i = 1; i < ${#files[@]}; i++)); do
  prev="${files[$i - 1]}"
  curr="${files[$i]}"
  if cmp -s "$prev" "$curr"; then
    if [[ "$DRY_RUN" == "true" ]]; then
      echo "[dry-run] would remove $(basename "$curr") (same as $(basename "$prev"))"
    else
      echo "Removing $(basename "$curr") (same as $(basename "$prev"))"
      rm "$curr"
    fi
    removed=$((removed + 1))
  fi
done

if [[ "$DRY_RUN" == "true" ]]; then
  echo "Done. ${removed} duplicate(s) would be removed."
else
  echo "Done. ${removed} duplicate(s) removed."
fi