[BUG] .claude.json corruption from excessive write frequency + antivirus EPERM on Windows (single-session, no concurrency needed)

Resolved 💬 4 comments Opened Feb 26, 2026 by ConstructController Closed Feb 26, 2026

Environment

  • Claude Code version: 2.1.59
  • OS: Windows 11 Pro
  • Install method: Native
  • Antivirus: Enterprise endpoint protection (EDR) running as a system service

Summary

.claude.json is being written thousands of times per session — even when data hasn't changed — at a rate of ~6-8ms between writes. When antivirus software briefly locks the file during a scan, the atomic rename fails with EPERM, the non-atomic fallback creates a partial-read window, and the corruption-detection logic resets the entire config to defaults. This destroys all user preferences, project history, onboarding state, and theme settings.

Key finding: This does NOT require multiple concurrent sessions. The first corruption event occurred with only a single Claude Code process running (verified via debug logs — no other PIDs were writing at the time).

Root Cause Chain

1. Excessive write frequency (underlying issue)

Claude Code saves .claude.json on virtually every UI render cycle. In a single session:

| Metric | Value |
|--------|-------|
| Total config writes (single session, ~1.5 hours) | 10,340 |
| Writes before first corruption | 749 |
| Writes after corruption (feedback loop) | 9,591 |
| Writes per minute (normal) | ~8/min |
| Writes per minute (post-corruption) | ~115/min |
| Identical consecutive writes (same 38K data) | Hundreds |

For comparison, sessions on previous days without corruption averaged 485-702 writes over 5+ hour sessions.

The writes appear tied to React/Ink render cycles (useDeferredValue, BackendRegistry checks). In a 1-minute sample window: 77 render events triggered 470 config writes (~6 writes per render), most writing identical 38,269-byte content.

2. Antivirus blocks the atomic rename (trigger)

The EDR process periodically scans .claude.json, holding a file handle that prevents the rename operation:

[ERROR] Failed to write file atomically: Error: EPERM: operation not permitted,
  rename '...\.claude.json.tmp.XXXXX.1772115905757' -> '...\.claude.json'
[DEBUG] Cleaning up temp file: ...\.claude.json.tmp.XXXXX.1772115905757
[DEBUG] Falling back to non-atomic write for ...\.claude.json
[DEBUG] File ...\.claude.json written successfully with non-atomic fallback

89 EPERM rename failures occurred in this single session. Most of the time the rename succeeds, but the AV occasionally grabs the file during the rapid-fire write cycle.

3. Non-atomic fallback enables corruption (the bug)

When the atomic rename fails, Claude falls back to a direct (non-atomic) write. During this non-atomic write, another async operation within the same process reads the file and gets truncated JSON (Unexpected EOF).

4. Corruption detection resets config to defaults (the cascade)

The truncated read triggers the corruption handler, which replaces the in-memory config with a near-empty object. This empty config is then written back to disk at the same frantic rate, permanently destroying the original data:

14:25:05.764 - Temp file written successfully, size: 38269 bytes  ← last good write
14:25:05.772 - Temp file written successfully, size: 77 bytes     ← entire config replaced
14:25:05.778 - Temp file written successfully, size: 77 bytes     ← cascading
14:25:05.783 - Temp file written successfully, size: 157 bytes    ← slowly rebuilding
14:25:05.839 - Temp file written successfully, size: 157 bytes
14:25:05.881 - Temp file written successfully, size: 193 bytes
14:25:05.886 - Temp file written successfully, size: 588 bytes

The 77-byte file contained only: {"clientDataCache":{"data":{},"timestamp":...}}

The config then slowly regrows as properties are added back (77 → 157 → 588 → 671 → 770 → 848 → ...) but critical keys like numStartups, theme, hasCompletedOnboarding, oauthAccount, and projects (with all project-specific settings) are permanently lost.

5. Lock contention within the same process

420+ Lock file is already being held errors across sessions confirm that even the internal locking mechanism can't keep up with the write rate. Multiple async save operations within the same process race each other.

Impact

  • User forced to re-login (OAuth account data lost)
  • All preferences reset (theme, onboarding, tips history)
  • All project-specific settings lost (tool approvals, trust dialogs, etc.)
  • 24+ corrupted backup files generated in a single day

Suggested Fixes

  1. Debounce config writes — The config should not be written on every render cycle. Batch changes and write at most once per second (or on meaningful state changes only). Writing identical data hundreds of times consecutively is pure waste.
  1. Don't fall back to non-atomic writes — The non-atomic fallback is the direct cause of corruption. If the atomic rename fails due to EPERM, retry after a short delay rather than falling back to a direct write that creates a partial-read window.
  1. Don't reset to defaults on read failure — If the config fails to parse, the in-memory state should be preserved (it was valid moments ago). Only read from disk on startup; use in-memory state during the session. At minimum, validate that the "new" config has at least as many keys as the old one before replacing it.
  1. Use proper file locking on WindowsLockFileEx / advisory locks to serialize access, especially for the non-atomic fallback path.

Reproduction

  1. Run Claude Code on Windows with any enterprise antivirus/EDR that performs real-time file scanning
  2. Use the tool normally (the excessive write rate is the default behavior)
  3. Wait for the AV to scan .claude.json at the wrong moment during rapid writes
  4. Observe EPERM errors in debug log, followed by non-atomic fallback, followed by config corruption cascade

Running multiple sessions increases the probability but is not required — a single session with AV is sufficient.

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗