/model save-as-default rewrites all of settings.json from a stale session snapshot, silently reverting hooks
Summary
Accepting the "saved as your default for new sessions" prompt from /model rewrites the entire user settings.json, and it writes a snapshot the session is holding rather than merging into the file currently on disk. A long-lived or resumed session therefore reverts settings.json to whatever it looked like when that session last loaded it, silently discarding everything added since.
In my case that silently removed five PreToolUse/PostToolUse hook entries, which are security and verification guards. They ran disarmed for ~3 days across every session on the machine.
Environment
- Claude Code v2.1.269 (also installed and running during the window: 2.1.267, 2.1.268)
- Linux, Ubuntu 26.04, bash
~/.claude/settings.jsonis a symlink into a git-tracked dotfiles repo, which is what made the forensics possible
What happened
~/.claude/settings.json contained a hooks block with 19 local hook entries. After the write, 5 were gone. The file was still valid JSON and the model key held the newly chosen value.
Why this report is diagnostically useful
The dotfiles repo gives an exact first-seen date for every hook entry, which bisects the snapshot age precisely:
| | When first added to settings.json |
|---|---|
| The 5 entries that vanished | 2026-09-07 22:16 → 2026-09-08 00:18 |
| The 12 entries that survived | 2026-08-27 and earlier |
No exceptions in either direction. That rules out partial writes, truncation, and random corruption. The written content is a coherent settings.json from a specific point in time.
Three independent signals for the mechanism
- Point-in-time content — the date separation above.
- Serializer fingerprint — every
\uXXXXescape in the file became literal UTF-8 in the same write. Node'sJSON.stringifyemits literal UTF-8; Python'sjson.dumpescapes by default. The file had been written with escapes previously, so this write came from the CLI's own serializer, i.e. an intentional complete save rather than an external edit. - Forward model, backward everything else — the
modelkey moved forward to the newly selected value while thehooksblock moved backward. That is a current change applied to a stale base, which is the signature of snapshot-write rather than read-modify-write against disk.
I also scanned every session transcript for Write/Edit/Bash tool events touching the path in the window. There were none, so no agent edited it.
Timeline
| Time (UTC) | Event |
|---|---|
| 2026-09-08 04:16 → 06:18 | Five hook entries added to settings.json and committed |
| 2026-09-08 15:17 | A session originally started 2026-08-13 is resumed |
| 2026-09-10 14:30:46 | /model in that session → CLI reports "Set model to Fable 5.1 and saved as your default for new sessions" |
| 2026-09-11 | Missing hooks noticed by an unrelated automated review |
Reproduction
- Put a
hooksblock in~/.claude/settings.json. - Start a Claude Code session and leave it open, or resume an older one.
- From a different context, add a new hook entry to
settings.jsonon disk. - Back in the long-lived session, run
/model, pick a model, accept the save-as-default prompt. git diff(or otherwise inspect)settings.json. The entry added at step 3 is gone;modelcarries the new value.
A save from a freshly started session does not reproduce it, since that session's snapshot matches disk. I confirmed this: a save from a new session after restoring the hooks left all 19 intact.
Impact
Ordinary config loss is annoying but visible. Hook loss is not, and that is what makes this worse than it looks:
- A hook that never fires and a hook that is absent are indistinguishable from inside a session. Nothing logs, warns, or differs.
- The entries lost here gate command execution and completion claims. Every session on the machine ran without them for ~3 days and looked completely normal.
- Anything that protects
settings.jsonby living insettings.jsonis removed by the same write, so the CLI cannot self-check this.
Relationship to existing issues
This looks like the same underlying mechanism as a family of reports where settings.json ends up retaining the model key and losing the rest:
- #76749 — "settings.json overwritten with valid
{"env": {}}— stale in-memory config re-persisted". Same diagnosis, also triggered around/model, also identified via a CLI serializer fingerprint. Closednot plannedby the stale bot on 2026-09-03 with "please open a new issue if this is still relevant", which is why I am filing rather than commenting. That report was Windows with ~15 concurrent sessions; this one is Linux with a single long-lived session, so the concurrency and the Windows atomic-rename family are not required to trigger it. - #93469 —
~/.claudereset withsettings.jsonreduced to{"model": "opus[1m]"}. Different blast radius, but again the model key survives and the rest does not.
If those are the same bug, this report's contribution is the clean bisect: the surviving/lost split by add-date shows the written content is a coherent older snapshot, which points at snapshot-write rather than at filesystem-level corruption.
Suggested fix
- Re-read
settings.jsonfrom disk immediately before a settings-mutating command writes it, and merge only the key being changed. - Failing that, write only the changed key rather than serialising a whole in-memory config object.
- Consider warning when a save would remove top-level
hooksorpermissionsentries that exist on disk, since those are the security-relevant ones and their absence is undetectable at runtime.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗