claude.json corrupted by concurrent writes (no atomic write / file locking)
Bug
~/.claude/claude.json gets corrupted when multiple claude processes run concurrently. The file ends up truncated (partial JSON), causing "JSON Parse error: Unexpected EOF" on next startup.
Environment
- Claude Code v2.1.59
- Windows 11 Pro (Git Bash / MINGW64)
- Multiple
claude -psubprocesses running via scheduled automation (cron-style jobs that invokeclaude -pfor background tasks)
Reproduction
- Have 2+
claudeprocesses running concurrently (e.g., a foreground session + aclaude -pbackground invocation) - Both processes read/write
claude.json(growth book features, client data cache, tool usage stats, etc.) - The file gets truncated mid-write, producing invalid JSON
This happens reliably when concurrent processes exist. In my case, scheduled background jobs spawn claude -p subprocesses, and when two overlap they corrupt the shared config.
Evidence
227 corrupted backups in ~/.claude/backups/ over ~6 days. Corruption clusters at times when multiple processes run:
- 68 corruptions in a single minute (1:58 AM, during scheduled batch processing)
- Corrupted files range from 0 bytes to ~5.9 KB (full valid file is ~5.9 KB)
- The smallest corrupted files (77-306 bytes) are clearly truncated mid-write -- valid JSON start, sudden EOF
- Some "corrupted" backups are actually complete valid JSON (the detection/backup mechanism fires even on valid files sometimes)
Example of a truncated file (123 bytes):
{
"cachedGrowthBookFeatures": {
"tengu_session_memory": false
},
"clientDataCache": {
"data": {},
Root Cause
The config writer does a non-atomic write (open, truncate, write). When two processes write simultaneously:
- Process A opens + truncates the file
- Process B opens + truncates the file (now 0 bytes)
- Process A writes its data
- Process B writes its data, overwriting A's partial output
- Either process may produce a truncated result
Suggested Fix
Use atomic writes: write to a temp file (claude.json.tmp), then rename() to claude.json. On POSIX this is atomic; on Windows, use os.replace() or equivalent. Optionally add advisory file locking for the read-modify-write cycle.
This is a standard pattern for config files that may be accessed by concurrent processes.
Workaround
Manually restoring from the largest .corrupted backup file in ~/.claude/backups/ works, but the corruption recurs on the next concurrent access.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗