Backup retention (keep-last-5) evicts the last good .claude.json after concurrent-write corruption, making config loss permanent

Status Open
Maintainer reply None cached
Activity 1 comment · opened Jul 17, 2026

Summary

The keep-last-5 backup retention that was requested in #29143 is now in place, but it counts corrupted backups toward the cap. Because concurrent-write corruption produces backups at the same rate as healthy writes, a handful of bad launches silently evicts every good .claude.json copy — turning a recoverable corruption into permanent, unrecoverable config loss.

The underlying race from #29143 / #29056 / #2593 still reproduces on v2.1.212 (those reports were against v2.1.59, ~150 releases ago). I'm not re-filing the race itself — this is about the recovery path no longer being able to save you.

Environment

  • Claude Code v2.1.212 (native install)
  • Windows 11 (10.0.26200)
  • Two separate config dirs via CLAUDE_CONFIG_DIR (multi-account setup), each hit independently
  • 13 concurrent claude.exe processes at the time (UI sessions + daemon + bg workers)

What happened

.claude.json (~57 KB) was replaced by a 50-byte skeleton containing exactly one key:

{
  "firstStartTime": "2026-07-17T08:49:39.273Z"
}

That is a process initialising a fresh config — i.e. it read the file as absent/unparseable mid-write and started over. Timeline from file mtimes:

| time | .claude.json | note |
|---|---|---|
| 14:17:00 | 57,162 B | healthy |
| 14:19:39 | 50 B | skeleton written over it |
| 14:19:40 | 963 B | onboarding stub after next launch |

The next launch prints Claude configuration file not found at: ... and drops into the first-run onboarding wizard (theme picker), despite .credentials.json being intact and the account still logged in. Completing that wizard writes another stub — so the natural user reaction actively makes it worse.

The regression: junk backups evict good ones

backups/ retains 5 .claude.json.backup.<epochms> files. Nothing distinguishes a 57 KB real config from a 50-byte skeleton — they rotate identically. Observed on one dir immediately after the incident:

1784278412802   57,205 B   14:21:00   <- good
1784278179506       50 B   14:19:39   <- junk, occupying a slot
1784278260039   57,162 B   14:17:00   <- good
1784278020522   57,162 B   14:10:48   <- good
1784277594134   55,956 B   14:08:26   <- good

1 of 5 slots already lost to junk. Five failed starts in a row and all five slots hold skeletons — the real config is gone with no recovery path.

This is not hypothetical. On my second config dir, the known-good backup I recovered from earlier the same day had already been evicted by later writes a few hours afterward. The recovery window is measured in launches, and corruption spends them fast.

Why this is worse than the original bug

#29143 asked for "Bounded backup file retention (e.g., keep last 5, not accumulate hundreds)". Unbounded backups were noisy but always retained a good copy. Bounding them fixed the noise and removed the guarantee. Failure mode moved from wastes disk to loses your config permanently.

Expected behaviour

Any one of these would close the data-loss hole, independent of fixing the race itself:

  1. Validate before backing up. Don't rotate in a backup that fails a sanity check (parses as JSON, has oauthAccount/projects, is not just firstStartTime). A corrupt current file is not worth a slot.
  2. Retain the last known-good separately. e.g. .claude.json.lastgood, only replaced by a config that passes validation. One extra file, and the failure becomes fully recoverable.
  3. Size/shape floor for eviction. Never evict a larger valid backup in favour of a smaller invalid one.
  4. Don't onboard on a failed read. Distinguish "no config (new user)" from "config unreadable/suspect (existing user)". The second should refuse to write a stub and point at backups/ — it already prints the path, then overwrites the slot anyway.

Fixing the write race (atomic temp+rename, or file locking) is the real fix and is tracked in #29143 — but as long as the race exists, the retention policy is what decides whether users can recover, and right now it decides "no".

Repro sketch

  1. Run several concurrent Claude Code sessions sharing one config dir (Windows).
  2. Wait for .claude.json to be replaced by the firstStartTime-only skeleton.
  3. Launch/exit ~5 more times.
  4. ls backups/ — every retained backup is a skeleton; the real config is unrecoverable.

Related: #29143 (closed as duplicate), #29056 (closed as not planned), #2593.

<sub>Refiled from #78437, which I opened from the wrong account.</sub>

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗