Windows: ~/.claude.json cascading corruption when multiple CLI + Chrome MCP processes run concurrently

Resolved 💬 3 comments Opened Feb 26, 2026 by zp184764679 Closed Feb 26, 2026

Bug Description

On Windows, ~/.claude.json suffers cascading corruption ("JSON Parse error: Unexpected EOF") when multiple Claude Code processes write to it concurrently. The file write is non-atomic on Windows (truncate → write → close), and concurrent readers can observe the truncated intermediate state.

Environment

  • OS: Windows 11 Pro (10.0.26200)
  • Claude Code: 2.1.59
  • Shell: Git Bash (MINGW64)

Reproduction

  1. Have Claude Desktop app running (Electron, spawns 4 child processes)
  2. Chrome extension active → spawns 4 claude.exe --chrome MCP server processes
  3. Open one or more CLI sessions (claude in terminal)
  4. Use tools in the CLI session (each tool call triggers a .claude.json write via toolUsage tracking)
  5. Within seconds, corruption cascade begins

Note: Running 8 CLI-only sessions previously worked fine. The issue appeared after Chrome MCP processes (--chrome flag) were added — they run persistently in the background, increasing concurrent write pressure.

Evidence

718 corrupted backups generated in ~2 minutes

The ~/.claude/backups/ directory accumulated 718 corrupted backup files in a single session startup. The session-start cleanup removed 429 on first pass; more continued to accumulate.

File sizes show classic write-race cascade

Timestamp (ms)          Size (bytes)
.corrupted.1772077343107   3054
.corrupted.1772077343110   3054
.corrupted.1772077343124   3092
.corrupted.1772077343146   3128
.corrupted.1772077343147   3128
.corrupted.1772077343165   3164
.corrupted.1772077343166   3164
.corrupted.1772077343180   3195
.corrupted.1772077343195   3230
.corrupted.1772077343215   3262
.corrupted.1772077343243   3292
.corrupted.1772077343259   3325
.corrupted.1772077343275   3361
.corrupted.1772077343303   3398

Pattern:

  • Files come in pairs at the same millisecond (two readers hitting the race simultaneously)
  • Sizes increment by ~30-36 bytes per pair — each cycle CC writes slightly more data
  • The 20+ corrupted files span only 197 ms — a tight cascade loop

.claude.json actively rewritten on every tool call

Monitoring shows the file changing size every ~1-2 seconds during active use:

11:50:33.889 SIZE_CHANGED: 0 -> 391
11:50:36.180 SIZE_CHANGED: 391 -> 440

Fields that trigger writes on every tool invocation:

  • toolUsage.{tool}.usageCount / lastUsedAt
  • clientDataCache.timestamp
  • lastCost, lastTotalInputTokens, lastSessionMetrics, etc.

State loss after cascade

After the cascade, .claude.json was reduced from ~3054 bytes (full config with oauthAccount, projects, githubRepoPaths, etc.) to 292 bytes (minimal skeleton). The "corrupted" backups actually contained the complete valid config.

Similarly, settings.local.json (project-level) was reset from 114 KB (416 accumulated permission entries) to 1.1 KB, losing all user-approved permissions.

Process inventory during corruption

claude.exe (Electron Desktop)   - 4 processes (main, gpu, network, crashpad)
claude.exe --chrome (MCP)       - 4 processes (one per Chrome tab group)
claude.exe (CLI session)        - active session

Root Cause Analysis

  1. On Windows, fs.writeFileSync() performs open(O_TRUNC) → write → close, which is not atomic
  2. Between open(O_TRUNC) (file now 0 bytes) and write completion, any concurrent reader sees truncated content → "Unexpected EOF"
  3. CC detects corruption → backs up truncated file → restores from backup → writes again → triggers another watcher event → cascade
  4. Each cycle adds more fields to the serialized JSON, explaining the incrementally increasing file sizes
  5. The --chrome MCP processes run persistently and write concurrently with CLI sessions and the Desktop app

Suggested Fix

Use atomic write on Windows: write to a temp file in the same directory, then fs.renameSync() to replace the target. On Windows NTFS, rename within the same volume is atomic. This is a well-known pattern (e.g., write-file-atomic npm package).

// Instead of:
fs.writeFileSync(configPath, JSON.stringify(data));

// Use:
const tmpPath = configPath + '.tmp.' + process.pid;
fs.writeFileSync(tmpPath, JSON.stringify(data));
fs.renameSync(tmpPath, configPath);  // atomic on NTFS

Alternatively, use a file lock (proper-lockfile or similar) to serialize concurrent access.

Workaround

Reduce concurrent writer count by closing unused Chrome tab groups (each spawns a --chrome MCP process), or temporarily disabling the Chrome extension.

View original on GitHub ↗

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