[BUG] Two concurrent sessions share ~/.claude — the Write tool silently overwrites another session's memory and rule files

Status Open
Reported on v2.1.200
Maintainer reply None cached
Activity 0 comments · opened Aug 10, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

~/.claude is global, mutable state shared by every session on the machine — CLAUDE.md,
rules/*.md, skills/**, and projects/<project>/memory/*.md. Two sessions running in
different working directories, on unrelated projects, write into the same files.

There is no locking, no conflict detection that stops a write, and no merge. The last
writer wins, silently. Nothing in either session's transcript shows that work was lost.

Measured behaviour

Measured with the file modified externally between the session's read and its write —
from the file's point of view that is exactly what a second session does.

| Tool | foreign change made after this session read the file | result |
|---|---|---|
| Edit (targeted, old_string/new_string) | on a region the edit does not touch | survives — the edit is applied, the foreign line is still there |
| Edit | on the exact region being edited | fails loudly, "String to replace not found in file" |
| Write (whole file) | anywhere | destroyed, no error, no prompt — the tool reports "has been updated successfully" |
| [System.IO.File]::WriteAllText from a shell command | anywhere | destroyed; not a tool call at all, so no layer could even check |

So the targeted tool is safe by accident — it replaces a span rather than the file, and its
anchor doubles as an optimistic lock that fires in both directions. The whole-file tool has
no freshness check whatsoever, and a raw write from the shell has no layer that could hold
one.

Why the existing notification makes it worse

When the file had been read earlier in the session, a <system-reminder> does appear after
the external change. Its wording, verbatim:

<path> was modified, either by the user or by a linter. This change was intentional, so make sure to take it into account as you proceed (ie. don't revert it unless the user asks you to). Don't tell the user this, since they are already aware.

In the two-session case every clause of that is wrong:

  1. It was neither the user nor a linter — it was another session.
  2. The user is not aware, and the model is explicitly instructed not to mention it.
  3. The reminder arrives, and the subsequent Write still overwrites the change.

So the one signal that exists is mislabelled, suppressed from the user, and non-binding.
And it only fires for files this session had already read — a file another session
created produces no signal at all.

Please do not auto-close this as a duplicate of #29217

#58736 reported the MEMORY.md half of this in May. It was closed by the duplicate bot as
a duplicate of #29217, then locked, without a human ever responding. That call conflates
two different failure modes:

| | #29217 | this report |
|---|---|---|
| what happens to the file | truncated mid-write, invalid JSON | stays perfectly valid |
| how it is noticed | parse error, 165 corrupted backups | never — the content is simply gone |
| what is lost | the config, recoverable from a backup | durable knowledge, no backup, no record it existed |
| affected files | ~/.claude.json | markdown under rules/, skills/, projects/*/memory/ |

A corrupted file announces itself. A silently overwritten rule file does not: it is
well-formed, it is plausible, and the only copy of what it said was in the other session's
context, which is gone. That is what makes this worth a separate fix, and the bot's own
closing comment on #58736 says to file a new issue and reference it.

#29217, #53895, #53896 and #58736 are all closed as not planned, so there is nothing left
to comment on.

This is also the markdown counterpart of #40175 (~/.claude.json: "detects the stale write,
logs telemetry, writes anyway") and a concrete instance of the "last-writer-wins into one
shared tree" failure described in #77463.

What Should Happen?

At minimum, Write should behave the way Edit already does by accident: fail when the
file's mtime or hash changed since this session last read it, and say so, so the model can
re-read and merge. Silently discarding another writer's content is never the right default
for a file the product itself tells users to store durable knowledge in.

Beyond that, in rough order of value:

  • An advisory lock, or an atomic compare-and-swap on mtime/hash, for writes under ~/.claude.
  • Fix the reminder wording: don't assert the change was intentional or that the user knows,

when the writer is unknown. "This file changed on disk since you read it; re-read before
writing" is both true and actionable.

  • Make concurrent sessions visible to each other at all — see #77463.

Error Messages/Logs

There is no error. That is the report.

Write, after the file changed on disk since this session read it:
  "The file <path> has been updated successfully."
  -> the other session's content is gone at this point, in both transcripts silently.

Edit, same situation, when the foreign change hit the edited region:
  "String to replace not found in file."
  -> this is the correct behaviour, and the only one of the three writers that has it.

Steps to Reproduce

  1. Start two Claude Code sessions in two different project directories, at the same time.
  2. In session A, have it add a section to ~/.claude/rules/some-rule.md.
  3. In session B, whose context still holds the file as it looked before step 2, have it

rewrite the same file — e.g. "restructure that rule".

  1. Read the file: session A's section is gone. Neither session reports a conflict.

The same applies to ~/.claude/projects/<project>/memory/*.md and to MEMORY.md, where the
damage is worse: memory files are the only state that survives a session, and the index line
and the note are two separate files that must stay consistent.

Claude Model

Opus

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

2.1.200

Platform

Other

Operating System

Windows

Terminal/Shell

PowerShell

Additional Information

Related:

  • #58736 — the MEMORY.md half of this, bot-closed as a duplicate of #29217 and locked,

never answered by a human

  • #40175 — same mechanism in ~/.claude.json; detects the stale write and does not abort
  • #29217 — concurrent writes corrupting .claude.json (different failure mode, see above),

closed as not planned

  • #53895, #53896 — non-atomic writes to team config and to the message array, both closed

as not planned

  • #27941 — predecessor of #40175, closed as stale
  • #77463 — instance invisibility, predicts exactly this for non-git workflows
  • #79354 — background task of a single session overwriting with stale content

View original on GitHub ↗