`@`-mention synthetic Read injection doesn't satisfy Edit/Write 'must Read first' guard, forces redundant Read
Summary
When a file enters context via an @filepath mention in a user message, the harness injects a synthetic <system-reminder> that mimics a Read tool call + result (so the model has the file content without spending a real turn). However, this synthetic injection is not registered with the Read-tracker that Edit/Write consult before allowing modifications. The result: the model has the full file content, but the first Edit attempt is rejected with File has not been read yet. Read it first before writing to it., forcing a real Read call that returns identical bytes already in context.
Environment
- Claude Code (Windows desktop)
- Model: claude-opus-4-7
- OS: Windows 11
- Trigger:
@-mention of a file in a user message
Steps to Reproduce
- Start a fresh Claude Code session.
- Send a message containing
@path/to/some/existing/file.md(no priorReadof that file in this session). - The harness injects a
<system-reminder>of the form:
````
Called the Read tool with the following input: {"file_path":"…"}
plus a second reminder with the file's numbered contents (the standard Read result format).
- Ask the assistant to make a small edit to that file.
- Assistant calls
Editon the file.
Observed Behavior
Edit fails:
File has not been read yet. Read it first before writing to it.
The assistant then has to call Read on the exact same path. The Read returns byte-identical content to what was already in the synthetic injection. After that, Edit succeeds.
Net effect per @-mention-then-edit flow:
- One redundant Read tool call.
- The file's contents occupy context twice (once from the synthetic injection, once from the real Read).
- One extra round-trip of latency.
- Confusing for the model: the system-reminder is formatted as if a Read occurred, so the model reasonably assumes Edit will work.
Expected Behavior
The synthetic Read injection produced by @-mention attachment should register the file with the same Read-tracker that Edit/Write check. The Edit guard should treat the file as already read (with the same staleness/mtime semantics as a real Read), so no redundant Read is necessary.
Why this matters
The cost is not just "one extra tool call" — the two copies of the file's content are not deduplicated:
- The synthetic
<system-reminder>injection is one text block in the message history. - The real
Readtool result is a separate text block in the message history. - Both blocks are replayed to the model on every subsequent turn for the rest of the session (until compaction).
- Prompt caching reduces $ and latency on the cached prefix, but cached tokens still count against the context window — so caching does not mitigate the context-pressure cost.
Per @-mention-then-edit flow, the costs are therefore:
- Context-window pressure: the file's full content occupies the window twice for the remainder of the session. For large files (long source files, generated docs, transcripts pulled in via
@) this is the dominant cost — it shortens how much real work fits before compaction. - Wasted tokens on every subsequent turn (both copies billed against the cache-miss and cache-hit lines).
- Wasted latency: one extra tool round-trip before any useful work begins.
- Wasted permission prompts in stricter permission modes — the redundant Read may itself need approval.
- Model confusion: the synthetic injection is presented in the exact shape of a real Read tool result, which makes the subsequent rejection feel like an inconsistency in the harness rather than a deliberate guard. A model that trusts the injection's framing will issue Edit first, hit the rejection, and have to recover.
Related Issues (same root cause — Read-tracker keyed too narrowly)
The Read-tracker only counts literal Read tool invocations. Several other paths put content in context but bypass the tracker:
- #47904 —
ExitPlanModeresets the Read-tracker for files created/read during plan mode - #53525 — Edit rejects "must be read first" after
Bash/Writewrote the file in the same session - #21291 (closed) — Read state lost after user-message interruption
- #34026 (closed) — Read state lost in long conversations
- #17895, #18158, #16182 — Various Write/Edit "must read first" rejections after Read
This issue is another instance of the same family: the tracker should be unified across every path that legitimately puts a file's content into the model's context.
Suggested Fix
Whichever code path constructs the @-mention synthetic Read system-reminder should also call the same internal "mark file as read at mtime T" routine that the real Read tool calls. After that, Edit/Write guards will accept the file without a redundant Read.
Minimal Repro Transcript (sanitized)
User message:
@README.md please add a heading at the top called "Notes"
Assistant tool call:
Edit(README.md, …)
Tool result:
File has not been read yet. Read it first before writing to it.
Assistant has to then call Read(README.md), which returns the bytes already present in the prior <system-reminder> injection, before retrying Edit.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗