You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Race condition: .claude.json corrupted by concurrent writes from multiple sessions #29217
.claude.json gets repeatedly corrupted when multiple Claude Code sessions run concurrently (or are rapidly restarted). The file is truncated mid-write, producing JSON Parse error: Unexpected EOF.
Evidence
Over a ~40 minute window (Feb 26, 14:54–15:35), 165 corrupted backup files were generated in ~/.claude/backups/. The corruption cascades — each recovery attempt gets corrupted again by a competing process, producing progressively smaller truncated files:
-rw-r--r-- 11873 Feb 26 14:15 .claude.json.corrupted.1772095551270 # full config
-rw-r--r-- 469 Feb 26 14:16 .claude.json.corrupted.1772095565563 # partially recovered
-rw-r--r-- 157 Feb 26 14:16 .claude.json.corrupted.1772095562170 # barely anything
-rw-r--r-- 77 Feb 26 14:16 .claude.json.corrupted.1772095562117 # just opening brace
Files as small as 77 bytes (just {"numStartups":1,"installMethod":"native","autoUpdates":false}) show the file being read mid-write by another process.
Impact
Token waste: Each corrupted restart triggers re-initialization (feature flag fetches, re-auth, session setup), burning tokens on repeated failed startups
Config loss: The final state was a fresh config (numStartups: 1) replacing a real config (numStartups: 72) with all project history, theme settings, and tool usage data
User had to manually restore from .claude.json.backup
Root Cause
The file write to .claude.json is not atomic. When multiple processes write simultaneously, one truncates the file while another is mid-read or mid-write, producing invalid JSON.
Suggested Fix
Use atomic file writes: write to a temporary file (e.g., .claude.json.tmp), then rename() it to .claude.json. On most filesystems, rename() is atomic and prevents partial reads. Additionally, consider using file locking (flock / platform equivalent) to serialize access.
Environment
Windows 11 (via Git Bash)
Claude Code (native install)
Trigger: multiple sessions or rapid restarts from same directory
Reproduction
Open 2+ Claude Code sessions in the same directory simultaneously
Adding comprehensive data from a power-user setup (25 hooks, frequent subagent spawns via Task tool):
Scale of Impact
Date
Corruptions
Notes
Feb 20
1
First occurrence
Feb 21
57
Moderate hook usage
Feb 22
74
Feb 23
1
Light usage day
Feb 24
6
Feb 25
4
Feb 26
280
Heavy subagent + hook usage
Total
423
Smoking Gun: 21 Different userID Hashes
Extracted from the last 50 corrupted files — 21 distinct userID values. This proves multiple independent processes are writing their own state to the same file simultaneously. Each subagent (spawned via Task tool) appears to compute and write its own userID hash.
Growing sizes in time clusters (43 → 77 → 157 → 234 → 391 → 497 within seconds) = snapshots at progressive write stages
Backup/restore cascade: CC's own recovery (backup corrupted → restore from backup) is itself non-atomic, creating new corruption opportunities. This explains exponential growth: 1 → 57 → 280/day.
Workaround
Built a background guardian daemon that polls .claude.json every 500ms, maintains an atomic "golden copy" via os.replace() (maps to MoveFileExW on Windows), and restores within ~650ms of corruption. Available if anyone wants it.
Environment
Windows 11 (10.0.26200)
Claude Code v2.1.62 (native)
25 hooks (7 PreToolUse, 5 PostToolUse) — amplifies write frequency
Ran into this exact issue before — here's what we found.
What's happening: The .claude.json file write operation is not atomic. Multiple concurrent Claude Code sessions (or rapid restarts) perform read-modify-write cycles on the same file without file locking or atomic rename. Process A reads the file, Process B truncates and begins writing, Process A (or C) reads the partially-written truncated content, then writes that corrupted state back — creating a corruption cascade where each recovery attempt gets corrupted again by a competing writer.
What worked for us:
Replace direct fs.writeFile() with atomic write pattern: write to a temporary file in the same directory, then rename (mv) it to the target path. rename() is atomic on POSIX systems within the same filesystem. Additionally, use a file lock (e.g., proper-lockfile or fcntl advisory lock) to serialize concurrent access across processes.
Steps:
Replace all fs.writeFile('.claude.json', data) calls with an atomic write helper: write to '.claude.json.tmp.' first, then fs.renameSync() to '.claude.json'
Add advisory file locking (e.g., lockfile or flock) around the read-modify-write cycle to prevent concurrent sessions from interleaving
On startup, detect if a .tmp write file exists (indicating a crashed incomplete write) and clean it up rather than reading a potentially corrupt main file
Consider using a write-ahead approach: read from .claude.json, but if it fails JSON.parse, automatically fall back to .claude.json.backup before creating a fresh config
This isn't limited to multiple CLI sessions — it also happens between the CLI and Desktop app, which is harder for users to avoid.
Proof: Two different userIDs in corrupted files
The corrupted backups from my incident contain two distinct userIDs, confirming two separate Claude Code processes:
Process A: 508b4da3ba38eff5e8f373d2cad7d2a1f3607524...
Process B: a0ff412df5211fd40b658fd462b7d70805e4b48a...
One from the CLI session, one from the Desktop app. The user doesn't even need to open multiple terminals — just having both the CLI and Desktop app running triggers it.
Collapse timeline: 77KB → 77 bytes in 3.5 minutes
TIME SIZE STATE
20:21:25 77,118 Full config (327 startups, 5 MCP servers, 42 projects)
20:21:47 193 DESTROYED — just { clientDataCache, userID }
20:21:56 428 Fresh session rebuilding (Bash:3, Read:2)
20:22:47 519 Two processes fighting...
20:24:01 157 Different userID writes even less
20:24:55 77 Minimum: just { clientDataCache: {} }
34 corrupted copies in 4 minutes. The "winner" was whichever process had the least data in memory.
Data loss quantified
Field
Before
After
MCP Servers
5
0 (all lost)
Projects
42
0
numStartups
327
1
promptQueueUseCount
14,558
0
Skill Usage Records
13 (225 total uses)
0
Tool Usage Entries
29
0
The MCP server configs included inline API keys — so the user also silently loses access to configured tools with no indication of what was lost.
@tsantoso79 That CLI + Desktop App race is an important addition — users can't reasonably avoid that scenario since they don't even realize both processes share the same .claude.json.
The two distinct userIDs in your corrupted backups are solid proof: 508b4da3... (CLI) vs a0ff412d... (Desktop App) — two separate processes doing concurrent read-modify-write with no coordination.
Workaround: event-driven broker daemon for Windows
Been dealing with this for weeks across 3-5 parallel sessions. After the backup folder hit triple digits I decided to stop restoring manually and build something that actually prevents the data loss instead of just detecting it after the fact.
The core idea: a PowerShell daemon that sits between Claude Code and .claude.json using FileSystemWatcher (event-driven, no polling). On every write it:
Acquires a system-wide Named Mutex — serializes access across all Claude Code processes, subagents, desktop app, everything
Validates the incoming JSON
Deep-merges changes against a shadow copy of the last known good state
Writes back atomically via [System.IO.File]::Replace()
The deep-merge matters because with multiple sessions writing concurrently, a simple "restore last good backup" throws away whatever the other session just wrote. The merge preserves changes from all sessions — nested objects are merged recursively, only arrays get replaced wholesale.
Detection-to-repair takes ~100-200ms. No admin elevation needed, runs entirely in user-space. PowerShell 5.1+ (ships with Windows).
Obviously this is a band-aid until Anthropic ships atomic writes upstream (the temp + rename pattern @sstklen described). But it's been stable for us in daily use and completely eliminated the corruption cascades @DomenicoDomotz documented.
Limitations: Windows only (Named Mutex is a Win32 API). The repo also includes a Bun preload approach (Layer 1) that patches fs.writeFileSync at runtime, but that's currently inactive because Claude Code compiles with --no-compile-autoload-bunfig.
We are seeing the same non-atomic write race affect ~/.claude/settings.json as well. A PreToolUse hook registered by one session was silently reverted when a concurrent session wrote its own changes to settings.json. No error, no notification — the hook just disappeared.
Same fix applies: atomic writes (temp file + rename) for all shared config files, not just .claude.json.
Seeing this on a recent Claude Code build (2.1.112, Windows 11 via Git Bash). Adds another data point with a slightly different cascade pattern than the original report — thought it might be useful.
Corruption timestamps on one machine over two calendar days:
Apr 15 16:16 — 1 file (40759 bytes, last good state)
Apr 15 18:10 — 4 files (24082 bytes each, same minute)
Apr 15 21:32 — 3 files (23662 bytes each, same minute)
Apr 15 21:41 — 4 files (23794 bytes each, same minute)
Apr 16 00:45 — 3 files (24157 bytes each, same minute)
Clustered multi-file writes: three or four .corrupted.* snapshots land in the same minute, each within milliseconds of the next, sizes identical to the byte. That looks like multiple processes each attempting a recovery-write on the same invalid state simultaneously, with each write racing to rename-over-truncated the others.
Cumulative content loss across cascades: size drops from the last good state at 40759 → 24082 → 23662 bytes, and never recovers. Each corruption event strips more of the previous state than it recovers. The recovered file always ends up smaller than whichever .corrupted.* snapshot is largest in the cluster.
Specific content loss observed:
The root mcpServers object was entirely removed from the recovered file — three stdio server entries including their env blocks (API tokens) were gone. Project-level entries elsewhere in the file (under /projects/<path>) survived. Appears the partial-recovery path preserves some scopes and drops others non-deterministically.
Recovery path that worked:
The 40759-byte .corrupted.* from the first event was still valid JSON. Merged its mcpServers back into the post-recovery file with a small Python script; restart picked up the servers as expected. Takeaway: the oldest .corrupted.* in the cascade is usually the most recoverable, but users have to know to look for it.
Multiple Claude Code sessions active concurrently (common workflow across a few projects)
Atomic write + tmp-and-rename as originally suggested would address both patterns above. Adding a flock or PID-based guard during the recovery path would also help — right now a recovery write can itself be raced by a second instance that sees the invalid state and spawns its own recovery write.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.
Activity
DomenicoDomotz commentedon Feb 27, 2026
Additional Forensic Evidence (7-day dataset, 423+ corruptions)
Adding comprehensive data from a power-user setup (25 hooks, frequent subagent spawns via Task tool):
Scale of Impact
Smoking Gun: 21 Different
userIDHashesExtracted from the last 50 corrupted files — 21 distinct
userIDvalues. This proves multiple independent processes are writing their own state to the same file simultaneously. Each subagent (spawned via Task tool) appears to compute and write its own userID hash.Corruption Size Distribution (Feb 26, n=286)
Hourly Correlation with Tool Usage
Peak corruptions align exactly with peak tool/subagent activity.
Key Findings
Corrupted files are valid JSON with 1-3 keys (not garbled data), proving truncate-then-write pattern:
{"changelogLastFetched": 1772121843243} // 43 bytes {"clientDataCache": {"data": {}, "timestamp": 1772133973376}} // 77 bytesGrowing sizes in time clusters (43 → 77 → 157 → 234 → 391 → 497 within seconds) = snapshots at progressive write stages
Backup/restore cascade: CC's own recovery (backup corrupted → restore from backup) is itself non-atomic, creating new corruption opportunities. This explains exponential growth: 1 → 57 → 280/day.
Workaround
Built a background guardian daemon that polls
.claude.jsonevery 500ms, maintains an atomic "golden copy" viaos.replace()(maps toMoveFileExWon Windows), and restores within ~650ms of corruption. Available if anyone wants it.Environment
Proposed Fix Priority
write-file-atomic(or temp + rename) for.claude.jsonwritescallmesomesh commentedon Feb 27, 2026
Additional reproduction - heavy multi-session Windows user
Same issue. I run 5+ concurrent Claude Code sessions daily (one per workflow/project). The corruption happens consistently.
My setup
What I see
claude.json is corrupted: JSON Parse error: Unexpected EOFerror appears mid-conversation in terminals that aren't even the ones causing the writeImpact
Notes
toolUsage,cachedGrowthBookFeatures, andclientDataCachefields seem to be the most frequent write triggersautoUpdates: falsedoesn't prevent the writes+1 on the atomic write fix. Per-session write files merged on read would also work (each session writes to
.claude.json.{pid}, reads merge all).sstklen commentedon Feb 28, 2026
Ran into this exact issue before — here's what we found.
What's happening: The .claude.json file write operation is not atomic. Multiple concurrent Claude Code sessions (or rapid restarts) perform read-modify-write cycles on the same file without file locking or atomic rename. Process A reads the file, Process B truncates and begins writing, Process A (or C) reads the partially-written truncated content, then writes that corrupted state back — creating a corruption cascade where each recovery attempt gets corrupted again by a competing writer.
What worked for us:
Replace direct fs.writeFile() with atomic write pattern: write to a temporary file in the same directory, then rename (mv) it to the target path. rename() is atomic on POSIX systems within the same filesystem. Additionally, use a file lock (e.g., proper-lockfile or fcntl advisory lock) to serialize concurrent access across processes.
Steps:
📊 We found 4 similar cases in our knowledge base with the same pattern — this gives us high confidence in this analysis.
Let me know if this works for your setup — happy to help troubleshoot further. 🦞
🦞 Confucius Debug — community knowledge base for AI agent bugs. Free to search via MCP.
tsantoso79 commentedon Feb 28, 2026
Additional evidence: CLI + Desktop App race (not just multi-session CLI)
This isn't limited to multiple CLI sessions — it also happens between the CLI and Desktop app, which is harder for users to avoid.
Proof: Two different userIDs in corrupted files
The corrupted backups from my incident contain two distinct userIDs, confirming two separate Claude Code processes:
One from the CLI session, one from the Desktop app. The user doesn't even need to open multiple terminals — just having both the CLI and Desktop app running triggers it.
Collapse timeline: 77KB → 77 bytes in 3.5 minutes
34 corrupted copies in 4 minutes. The "winner" was whichever process had the least data in memory.
Data loss quantified
The MCP server configs included inline API keys — so the user also silently loses access to configured tools with no indication of what was lost.
Environment
sstklen commentedon Feb 28, 2026
@tsantoso79 That CLI + Desktop App race is an important addition — users can't reasonably avoid that scenario since they don't even realize both processes share the same
.claude.json.The two distinct userIDs in your corrupted backups are solid proof:
508b4da3...(CLI) vsa0ff412d...(Desktop App) — two separate processes doing concurrent read-modify-write with no coordination.Updated scope:
The atomic write fix (write to temp →
rename()) would cover all these cases sincerename()is atomic on both POSIX and NTFS.junaidtitan commentedon Mar 3, 2026
We kept hitting this with multiple Claude instances. Built cozempic to detect and auto-repair .claude.json corruption — open source.
Scans for invalid JSON, numStartups anomalies, and rapid backup file creation (the cascade pattern). Feedback welcome.
nemekath commentedon Mar 13, 2026
Workaround: event-driven broker daemon for Windows
Been dealing with this for weeks across 3-5 parallel sessions. After the backup folder hit triple digits I decided to stop restoring manually and build something that actually prevents the data loss instead of just detecting it after the fact.
The core idea: a PowerShell daemon that sits between Claude Code and
.claude.jsonusingFileSystemWatcher(event-driven, no polling). On every write it:[System.IO.File]::Replace()The deep-merge matters because with multiple sessions writing concurrently, a simple "restore last good backup" throws away whatever the other session just wrote. The merge preserves changes from all sessions — nested objects are merged recursively, only arrays get replaced wholesale.
Detection-to-repair takes ~100-200ms. No admin elevation needed, runs entirely in user-space. PowerShell 5.1+ (ships with Windows).
Repo: https://github.com/nemekath/claude-config-broker
Obviously this is a band-aid until Anthropic ships atomic writes upstream (the temp + rename pattern @sstklen described). But it's been stable for us in daily use and completely eliminated the corruption cascades @DomenicoDomotz documented.
Limitations: Windows only (Named Mutex is a Win32 API). The repo also includes a Bun preload approach (Layer 1) that patches
fs.writeFileSyncat runtime, but that's currently inactive because Claude Code compiles with--no-compile-autoload-bunfig.0reo commentedon Apr 4, 2026
We are seeing the same non-atomic write race affect
~/.claude/settings.jsonas well. A PreToolUse hook registered by one session was silently reverted when a concurrent session wrote its own changes to settings.json. No error, no notification — the hook just disappeared.Same fix applies: atomic writes (temp file + rename) for all shared config files, not just
.claude.json.This comment was written by Claude Code.
Prevent simultaneous mounts of the same volume
ProductOfAmerica commentedon Apr 17, 2026
Seeing this on a recent Claude Code build (2.1.112, Windows 11 via Git Bash). Adds another data point with a slightly different cascade pattern than the original report — thought it might be useful.
Corruption timestamps on one machine over two calendar days:
Two patterns beyond what #29217 describes:
.corrupted.*snapshots land in the same minute, each within milliseconds of the next, sizes identical to the byte. That looks like multiple processes each attempting a recovery-write on the same invalid state simultaneously, with each write racing to rename-over-truncated the others..corrupted.*snapshot is largest in the cluster.Specific content loss observed:
The root
mcpServersobject was entirely removed from the recovered file — three stdio server entries including theirenvblocks (API tokens) were gone. Project-level entries elsewhere in the file (under/projects/<path>) survived. Appears the partial-recovery path preserves some scopes and drops others non-deterministically.Recovery path that worked:
The 40759-byte
.corrupted.*from the first event was still valid JSON. Merged itsmcpServersback into the post-recovery file with a small Python script; restart picked up the servers as expected. Takeaway: the oldest.corrupted.*in the cascade is usually the most recoverable, but users have to know to look for it.Environment (matches #29217):
Atomic write + tmp-and-rename as originally suggested would address both patterns above. Adding a flock or PID-based guard during the recovery path would also help — right now a recovery write can itself be raced by a second instance that sees the invalid state and spawns its own recovery write.
github-actions commentedon May 25, 2026
Closing for now — inactive for too long. Please open a new issue if this is still relevant.
docs(architecture): document concurrent config-write safety (#5)
github-actions commentedon Jul 20, 2026
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.