I had one session sorting image assets for my wallpaper app while, in the window next to it, I edited copy for a client site. The next morning I came back to that second window and the MCP servers I had connected were gone from the list, and the workspace trust prompt appeared again as if I had never approved it.
My first suspicion was my own carelessness. I went back through every file I remembered touching the night before, and every line I had written was still there. It took three repeats before I noticed the pattern: what disappeared was always the change made by whichever session had edited settings first.
It was not my habits. Concurrent sessions were silently reverting each other's changes to ~/.claude.json, and it was fixed in Claude Code v2.1.259, released on September 2, 2026.
The short version of what to do now
Three steps, in this order.
- Check your version and upgrade if you are below v2.1.259.
- Find what is missing and put it back by hand. Nothing does this for you.
- Take one snapshot of the config file afterward, so the next disappearance is obvious.
claude --version
# Keep a copy of the current state before you upgrade
cp ~/.claude.json ~/.claude.json.$(date +%Y%m%d-%H%M%S).bakWhat the release fixed is future damage. Settings that were already dropped do not come back on their own, so upgrading and calling it done leaves you with quiet holes you will trip over later.
Every symptom looks like something you did wrong
That is what made this expensive for me. I spent three days auditing my own procedure.
| What you see | What you blame first | What points at a rollback |
|---|---|---|
| The workspace trust prompt returns | You reopened the project | You approved that exact path yesterday |
| MCP servers vanish from the list | A typo in your config file | The config file itself is untouched |
| Per-project state resets | A behavior change in the update | The other open session is unaffected |
Before I accepted that explanation I tried the cheap alternatives first, because they are cheap. I re-validated the JSON, which parsed fine. I checked file permissions and ownership, which were unchanged. I diffed my project-level configuration against git, which showed nothing. Each of those took a few minutes and each came back clean, and that accumulating cleanliness was itself the signal: when every input is intact and the output still changes, something outside your inputs is doing the writing.
The last row is what settled it for me. Of the two sessions I had open, exactly one reverted and the other was fine. A malformed config would hurt both equally. One-of-two is much easier to explain as something writing over the file from outside.
Why the later writer wins
~/.claude.json is read when a session starts and written back as a whole when something changes. That read-modify-write shape drops any edit another process made in between, without complaining. Databases have called this a lost update for decades.
The uncomfortable part is that the file it leaves behind is not broken. It is valid JSON, it parses, and nothing raises a warning. The contents are simply forty minutes old.
A corrupted config announces itself at startup; a well-formed but stale config stays quiet until the moment you need the setting.
Misspelled keys have the same shape of invisibility from a completely different cause, which I wrote about in A One-Letter Typo in settings.json Is Ignored Without a Single Warning. Different mechanism, same problem: the file is well-formed, so nothing stops you.
A small script for finding what went missing
The hard part of restoring is remembering what was there. I stopped relying on memory and started recording key paths so I could diff them.
#!/usr/bin/env python3
"""Record the key paths in ~/.claude.json and show what changed since last run."""
import json
import datetime
import pathlib
SRC = pathlib.Path.home() / ".claude.json"
SNAP_DIR = pathlib.Path.home() / ".claude-config-snapshots"
MAX_DEPTH = 3
def key_paths(node, prefix="", depth=0):
# Values may hold history and credentials, so collect paths only
paths = set()
if isinstance(node, dict) and depth <= MAX_DEPTH:
for key, value in node.items():
path = f"{prefix}/{key}"
paths.add(path)
paths |= key_paths(value, path, depth + 1)
return paths
def main():
if not SRC.exists():
print(f"Not found: {SRC}")
return
SNAP_DIR.mkdir(exist_ok=True)
with SRC.open(encoding="utf-8") as handle:
current = key_paths(json.load(handle))
snapshots = sorted(SNAP_DIR.glob("*.txt"))
if snapshots:
previous = set(snapshots[-1].read_text(encoding="utf-8").splitlines())
lost = sorted(previous - current)
gained = sorted(current - previous)
for path in lost:
print(f"GONE: {path}")
for path in gained:
print(f"NEW: {path}")
if not lost and not gained:
print(f"No changes ({len(current)} paths)")
else:
print(f"First run, nothing to compare ({len(current)} paths recorded)")
stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
target = SNAP_DIR / f"{stamp}.txt"
target.write_text("\n".join(sorted(current)), encoding="utf-8")
print(f"Recorded: {target}")
if __name__ == "__main__":
main()Two choices in there are worth explaining.
The first is that it reads key paths and never values. This file can hold conversation history and credential material, and I did not want any of that copied outside the file just so I could compare two states. Paths alone still tell you which project lost which setting.
The second is that it does not check for any named field. My first attempt looked directly for the field that records whether a directory has been trusted. But this file is not a published contract, and its shape moves between releases. A check written against a specific name reads a renamed field as "key absent, nothing wrong" and passes in silence — which is exactly the failure mode I was trying to detect. A key-set diff surfaces a rename too, as a matched pair of GONE and NEW lines.
From there I walk the output and rebuild each missing setting. For MCP servers I compare against claude mcp list and re-add what is absent. For trust, opening a session in that directory once will ask again.
One ordering detail saved me time on the second pass. Restore trust for a directory before you re-add MCP servers scoped to it, not after. Servers attached to a directory you have not yet approved will not come up, and you end up debugging a server definition that was never the problem. Approve first, then add, then confirm with claude mcp list in that same directory rather than from your home folder — scope is easy to lose track of when you are rebuilding several projects in one sitting.
It is also worth writing down what you restored, in whatever place you already keep notes. Half of my second morning went to re-deriving a server configuration I had reconstructed correctly the day before and then lost again to the same rollback, because I upgraded only after the second loss.
The boundary I kept after upgrading
I still do these three things, because what got fixed is Claude Code's side of the file — not my own backup and inventory scripts that also touch it.
- Changes to settings (adding an MCP server, approving trust, installing a plugin) happen in one session only, with the others closed.
- After any such change I run the script above and leave one snapshot behind. It takes a few seconds.
- Unattended scheduled jobs may read the config file but never write it. If a job needs to write, I move that job back to an interactive run.
The third rule is the one I expected to drop and did not. Read-only unattended jobs are genuinely useful — they can tell you which servers are configured, or that a directory lost its approval overnight — and keeping them read-only costs nothing until the day a job wants to fix what it found. That is the moment to move it back to an interactive run rather than granting it a write it will perform while three other sessions are open.
One mouth writes the config; any number of mouths may read it. On days when I work in parallel, that is the one line I hold to.
For handing a long job to a second session and waiting on it, I wrote down the arrangement I use in Handing a long job to another session — and the completion marker for when the notification never arrives. And for the same "second process barges in" problem approached from the lock-file side, there is When a lock left in a shared folder shuts out the second unattended run.
Your next step
Run claude --version. If you are below v2.1.259, upgrade, and then run the script once so you have a first snapshot on disk. The next time something disappears, you will spend a minute deciding whether it disappeared instead of an afternoon.
Having burned three mornings on this myself, I hope it shortens someone else's. Thank you for reading this far.