Windows: ~/.claude.json cascading corruption when multiple CLI + Chrome MCP processes run concurrently
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
- Have Claude Desktop app running (Electron, spawns 4 child processes)
- Chrome extension active → spawns 4
claude.exe --chromeMCP server processes - Open one or more CLI sessions (
claudein terminal) - Use tools in the CLI session (each tool call triggers a
.claude.jsonwrite viatoolUsagetracking) - 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/lastUsedAtclientDataCache.timestamplastCost,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
- On Windows,
fs.writeFileSync()performsopen(O_TRUNC) → write → close, which is not atomic - Between
open(O_TRUNC)(file now 0 bytes) andwritecompletion, any concurrent reader sees truncated content → "Unexpected EOF" - CC detects corruption → backs up truncated file → restores from backup → writes again → triggers another watcher event → cascade
- Each cycle adds more fields to the serialized JSON, explaining the incrementally increasing file sizes
- The
--chromeMCP 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.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗