Auto-memory subsystem writes to MEMORY.md mid-turn, invalidating Edit-tool read snapshots
<!--
Draft NEW issue to file at https://github.com/anthropics/claude-code/issues
Post with:
gh issue create --repo anthropics/claude-code \
--title "Auto-memory subsystem writes to MEMORY.md mid-turn, invalidating Edit-tool read snapshots" \
--body-file new-issue-memory-md-automemory.md \
--label bug
Note: this is NOT a duplicate of #3513. #3513's trigger is user-configured PostToolUse hooks.
This issue's trigger is the harness's own auto-memory subsystem, which fires WITHOUT any
user-configured hook. The two bugs share a symptom but have independent triggers and
require independent fixes.
-->
Summary
The auto-memory subsystem in Claude Code writes to ~/.claude/projects/<slug>/memory/MEMORY.md between turns (and possibly mid-turn) without notifying the read-snapshot tracker. When the model Reads MEMORY.md early in a turn and then Edits it later, the edit fails with:
<tool_use_error>File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.</tool_use_error>
The "user or linter" framing is wrong for this trigger — the harness modified its own file.
Not a duplicate of #3513
#3513 covers the case where user-configured PostToolUse hooks rewrite the file. This issue covers a different trigger that is part of the Claude Code harness itself:
- #3513 trigger: User-configured
PostToolUsehooks (prettier --write,dart format,markdown-formatter.py, etc.) modify the file afterEdit/Writecompletes. - This issue's trigger: The auto-memory subsystem fires
WriteagainstMEMORY.mdoutside the model's tool-call sequence, often between turns. The user has no hook configured; the harness owns this write.
Both produce identical errors. Both need fixes. They are independent.
Repro
- Use auto-memory (default on, file at
~/.claude/projects/<slug>/memory/MEMORY.md). - In a session:
Read MEMORY.mdearly to scan the index. - Later in the same session, after the model has saved a new memory file via
Write memory/something.md, attemptEdit MEMORY.mdto add the index pointer. - The
Editfails with "modified since read".
Or, simpler:
- Start a session. The session-start system reminder fires:
Note: MEMORY.md was modified, either by the user or by a linter. Read it again before attempting to write it. - No user action. No hook configured. The previous turn's auto-memory save updated
MEMORY.mdbetween turns.
Evidence
Scan of 3 weeks of JSONL transcripts (single user, macOS): MEMORY.md is the top hit across the 22 sessions hitting this error class, accounting for ~6 of them. Recovery pattern observed across sessions:
T+0 Read MEMORY.md
T+30s (memory save happens silently — Write to some memory/<topic>.md file)
T+45s Edit MEMORY.md (intends to add index pointer)
→ ERROR: "modified since read"
T+50s Read MEMORY.md ← forced recovery
T+55s Edit MEMORY.md → OK
The recovery Read succeeds and returns content the model has not seen change — confirming the modification was content-equivalent or invisibly small, with the only observable effect being the mtime bump that invalidated the snapshot.
In the most recent turn of my current session, the <system-reminder> fired at turn-start (before any tool call), indicating the inter-turn write happened with no in-turn trigger:
<system-reminder>
Note: /Users/<user>/.claude/projects/<slug>/memory/MEMORY.md 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...
</system-reminder>
No user edit. No hook. The harness's own auto-memory subsystem.
Why this matters
The auto-memory subsystem is on by default. The error class it triggers is treated by the model as something the user did (per the reminder's wording — "either by the user or by a linter"). This is misleading:
- The model spends tokens explaining the error to the user.
- The model spawns a recovery
Readthat wasn't logically required. - For agentic / orchestration workflows, the failed
Editcan cascade into broken state. - The error message blames the user for a write the harness performed.
Proposed mechanism
Two independent fixes — either suffices, both are good:
Fix A (preferred): synchronize the auto-memory subsystem with the read-snapshot tracker
When auto-memory writes to MEMORY.md:
- Acquire the same lock the tool layer uses for
Edit/Write. - After the write, refresh the read-snapshot for
MEMORY.md— both mtime and content hash.
This treats auto-memory writes as part of the harness's own internal coherence, not as foreign modifications. The model's next Edit succeeds without spurious failures, and a content drift (rare) would surface as a normal content-mismatch error.
Fix B (broader): clarify the error message and add provenance
If the harness can identify that the file was modified by its own subsystem (auto-memory, hooks, indexer), the error should say so:
<tool_use_error>
File was modified by Claude Code's auto-memory subsystem after your last read.
Read it again before attempting to write.
</tool_use_error>
This doesn't fix the spurious failure but stops blaming the user. Worth doing regardless of Fix A.
Relationship to #3513
#3513 needs a snapshot-refresh after PostToolUse hooks. This issue needs a snapshot-refresh after auto-memory writes. The underlying mechanism is the same (refresh the snapshot when the harness itself modifies the file) but the trigger surface is different. A clean fix would route both through a single internal API:
def harness_internal_write(path, content):
write(path, content)
snapshot_tracker.refresh(path)
…and have both auto-memory and the PostToolUse runner call it.
Environment
- macOS 26
- Claude Code CLI 2026-05 vintage
- Auto-memory enabled (default)
~/.claude/CLAUDE.md+ projectCLAUDE.md+ auto-loaded MEMORY.md- Reproduces with and without user-configured hooks
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗