[Bug] Settings file corrupted to `{}` by stale in-memory config persisting over concurrent writes

Status Open
Maintainer reply None cached
Activity 0 comments · opened Jul 29, 2026

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: write
settings.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:41six 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_id
com.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 12
UserPromptSubmit 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

  1. Re-read and compare before any full-object save. This is the one that actually fixes

it; the atomic write did not help.

  1. 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.

  1. Back up before overwriting when an existing settings.json parses to empty/default.

Cheap, and converts unrecoverable loss into an inconvenience.

  1. Warn when writing 3 bytes over a previously-larger file. The CLI already knows both


Note: Content was truncated.

View original on GitHub ↗