[Bug] Settings file corrupted to `{}` by stale in-memory config persisting over concurrent writes
Bug Description~/.claude/settings.json is being replaced with {} by Claude Code's own settings-save.
It happened here 5 times across 2 days on macOS. The write is a correct atomic
tmp+rename — so this is not file corruption, it is a stale in-memory config being persisted
over a good file.
Environment: macOS 26.5.2, zsh, currently v2.1.219. I cannot establish which version was
running at the moment of the first incident — it was not recorded, so I am not claiming one.
Multiple concurrent sessions are routine here (15 project directories under~/.claude/projects).
What happened
The file was replaced with exactly {} — 3 bytes, valid JSON, complete write. Not
truncated, not deleted. The mechanism was Claude Code's own save path: writesettings.json.tmp.<pid>.<hex>, then rename() over the target.
I can pin the replacement to the millisecond, because the destroyed inode's birth time was
captured inside a later filesystem audit event:
st_birthtimespec = 2026-07-21T17:57:47.934920369Z
Attribution — 16 ms after /model
17:57:47.918Z /model executed; CLI confirms "saved as your default for new sessions"
17:57:47.934Z settings.json inode REPLACED, new size 3 bytes ({})
That session had started at 17:57:41 — six seconds earlier. It had no opportunity to
read and hold the real configuration, which fits a startup read landing on an empty or
partial view and then being written back as authoritative on the /model save.
The CLI had just READ a settings file and printed its contents
The /model output at 17:57:47.918Z, 16 ms before the destruction, was:
Set model to Opus 4.8 (1M context) (default) and saved as your default for new sessions
.claude/settings.json pins Fable 5 — that applies on restart
So at the moment of the destroying save, the process had a successful read of a
settings file in hand and was quoting its model pin back to me.
To be precise about what this does and does not show: the path printed is.claude/settings.json with no ~, so this is plausibly the project-scope file, whereas
the file destroyed was the user-scope ~/.claude/settings.json. I am not claiming it proves
a good read of the user-scope file specifically.
What it does show is that the /model save path was actively reading and reasoning about
settings precedence at the exact millisecond it wrote {} over user settings. If the
model is "a session held a stale empty view", it is worth checking why that same code path
could simultaneously report a correct, non-empty read.
It repeated
Two days later the same mechanism wrote 3-byte {} via tmp+rename four more times —18:20Z, 18:56Z, 19:54Z, 20:16Z — process claude.exe, signing_idcom.anthropic.claude-code. Five destructive saves total.
The atomic write is not the bug, so making it more atomic will not fix it
This is the part I most want to land. On macOS the write path behaved correctly: an atomic
tmp+rename, old inode swapped cleanly. The config was destroyed anyway. The defect is the
read-merge-write cycle — a session reads an empty/partial view, holds it, and later persists
the whole object over whatever other sessions wrote in between.
GitHub issue #76749 reports the same variant independently on Windows and reaches the same
conclusion. It is currently labelled platform:windows, which I think is too narrow: this
macOS evidence shows it is not a Windows file-semantics problem.
Why this is worse than a config reset
~/.claude/settings.json is where hooks live. On this machine it registers 12UserPromptSubmit hooks that inject policy into every turn of every session.
When the file becomes {}, those hooks stop firing and emit nothing. A hook that no
longer runs produces no error, no warning, no log line — its absence is indistinguishable
from a turn where it ran and had nothing to say. The failure is silent at the moment of loss
and stays silent indefinitely. Anything a user has built on hooks — policy injection, safety
gates, formatting enforcement — is simply gone with no signal.
That is what made this an incident rather than an annoyance.
Suggested fixes, in the order this evidence supports
- Re-read and compare before any full-object save. This is the one that actually fixes
it; the atomic write did not help.
- Refuse to persist a config that lost keys it did not touch. A save that drops
hooks,
permissions and enabledPlugins when the session only changed model is never
legitimate, on any platform.
- Back up before overwriting when an existing
settings.jsonparses to empty/default.
Cheap, and converts unrecoverable loss into an inconvenience.
- Warn when writing 3 bytes over a previously-larger file. The CLI already knows both
…
Note: Content was truncated.