- Book: AI Agents Pocket Guide
- The series: Pocket Guides for Developers — 8 short books on agents, RAG, prompts, observability, events, databases and system design — the whole series
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
You delete a few lines in your editor and save. You tell the agent the file changed and ask it to look again. It calls Read and gets back one line: Unchanged since last read.
That is the repro in issue #69876, filed against Claude Code 2.1.185 on June 21, 2026. A Read of the whole file a moment later returned 33 lines, so the edit was on disk. The tool had answered from its own memory of the file. The thread has no reply from a maintainer. A bot labeled it stale on July 19 and closed it as "not planned" on August 2.
On November 3, Claude Code 2.1.317 shipped this line:
Removed the Read tool's "file unchanged since your last Read" stub: reading an unchanged file again returns its content
No reason is given, and no issue is linked.
The stub is gone, but the design question behind it applies to any agent harness that caches tool results. A read cache has to answer two questions, and the reports show it answering only one.
What the stub was and why it existed
The feature arrived in 2.1.86, released March 27: "Read tool now uses compact line-number format and deduplicates unchanged re-reads, reducing token usage". Agents re-read files a lot. They read, edit, then read again to check. Each full read puts the whole file back into the context window, so skipping a repeat read of an unchanged file saves real tokens.
Instead of the file, the model got a short note. Its wording changed across versions. Issue #55042, on 2.1.123, quotes: "File unchanged since last read. The content from the earlier Read tool_result in this conversation is still current — refer to that instead of re-reading." Issue #60722, on 2.1.140, quotes a blunter one: "Wasted call — file unchanged since your last Read. Refer to that earlier tool_result instead." In the terminal you saw the short form, Unchanged since last read.
Read the second wording again. It makes two claims. The file has not changed, and the earlier tool result is still there for the model to look at. Either claim can be false on its own.
Question one: did the file change on disk?
A dedup cache keys each entry by path and keeps something cheap to compare: the modification time, the size, or a hash of the content. On the next read it compares again and returns the stub if nothing moved.
The bug reports show the first claim failing when something other than the agent touched the file. Issue #60684 (2.1.144) got the stub after the user saved a notebook in their editor, then got the real content through a shell cat. Its author's theory is that the check tracked only the agent's own Edit and Write calls; no maintainer confirmed that. A bot closed it for inactivity, as it did #69876. Neither got a fix.
Mtime is a weak signal, because content can change while it stays put. Claude Code's own Edit check hit exactly that: the 2.1.295 release includes "Fixed Edit treating a file as fully read when its contents changed without its modification time advancing". A content hash avoids that, and it is cheap here. Hashing means reading the bytes from disk, which costs almost nothing. The expensive part is sending those bytes to the model, and a hash lets you skip only that part.
A hash has its own blind spot. Issue #45748 (2.1.96) got the stub after switching branches away and back with git checkout, which rewrote the file twice and landed on the same bytes. The reporter says the cache compared content hashes, so it saw nothing change. That issue was also closed by a bot. After an A-to-B-to-A round trip, "unchanged" is only safe if the model can still see A. That is the second question.

The cache stamps "unchanged" from memory. The disk already says otherwise.
Question two: can the model still see the earlier result?
This is the part a disk check can never answer. The stub tells the model to go back to an earlier tool result. That only works if the result is still in the context window.
Two things remove it. A rewind (Esc twice, or /rewind) rolls the conversation back to before the read. Issue #55042 did exactly that, then read five files in parallel: four came back as stubs pointing at results the rewind had just removed. Issue #46749 reported the same after compaction. When the context fills up, Claude Code compacts the conversation into a summary that "keeps your requests and key code snippets". A full file listing is not guaranteed to be one of them.
The workarounds in those threads were Read with an explicit offset and limit, which skipped the dedup, or reading through the shell instead. Both depend on the model noticing that it has been handed a pointer to nothing.
A community hook called read-once hit the same wall from outside. It blocks repeat reads too, and expires its entries after 20 minutes because, in its author's words, "There's no way to detect compaction events from a hook".
Inside the harness you can detect it. Here is a minimal cache that asks both questions. It is an illustration, not Claude Code's code:
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
type Seen = { hash: string; epoch: number };
const seen = new Map<string, Seen>();
let epoch = 0;
// Call this whenever compaction or a rewind
// removes earlier tool results from context.
export function contextDropped(): void {
epoch++;
}
export function read(path: string): string {
const text = readFileSync(path, "utf8");
const hash = createHash("sha256")
.update(text)
.digest("hex");
const prev = seen.get(path);
seen.set(path, { hash, epoch });
const same = prev !== undefined
&& prev.hash === hash
&& prev.epoch === epoch;
return same ? "File unchanged since last read." : text;
}
The hash answers question one. The epoch answers question two: any event that drops tool results from context invalidates every entry at once. It is still not complete. A partial read with a different range, a subagent with its own context, or a forked session each need their own answer, and every one you miss sends the model back to a result it no longer has.

Compaction keeps a summary. The full tool result the stub points at is gone.
What the removal costs
The cost is context. Issue #100265 measured it on 2.1.292: a second Read of a 26 KB file returned the full 27,807 characters again, and context went from 70,281 to 83,274 tokens. That is about 13,000 tokens for a file the model had already seen. A long session that re-reads big files fills its window sooner and compacts more often.
That issue also suggests many users stopped getting the stub before 2.1.317 shipped. Its author found no "file unchanged" result in 753 local sessions from 2.1.209 to 2.1.292. A commenter on 2.1.310 found "tengu_read_dedup_killswitch": true in the cached remote config inside their ~/.claude.json, with the stub last appearing in their transcripts under 2.1.286. That is one user's reading of their own config, not an official explanation. The 2.1.317 entry makes the behavior official for everyone.
Run claude --version, and claude update (CLI reference) if you are behind. If you still want fewer repeat reads, ask for line ranges on large files rather than the whole thing. A stale answer cost more than the tokens did: a model working from an old copy of a file writes code against lines that are no longer there.
Review the diff from disk
The model's picture of your repository is whatever came back in its tool results, filtered through compaction. When you review an agent's work, read the diff from git, which comes from the files.
I build Hermes IDE, so weigh this part with that in mind. Hermes runs Claude Code, Codex and other agent CLIs in real terminals, and its review side works from disk. Per the README, every agent turn is saved, so you can diff or restore any single turn, with undo, and the Review Desk (⌘G) shows changes by turn or by file. The same README says Hermes observes "what agents report on purpose and what git shows".

The Review Desk (⌘G) in By turn view for a Claude Code task in orders-api: two turns, turn 1's diff of src/db.ts, the changed-files list and a commit message drafted from the turns.
So if an agent works from a stale copy and writes the old lines back over your edit, the turn's diff in the Review Desk is where you would see your lines being removed, whatever the model believes it read. Clicking that turn in the strip of agent replies shows its changes or restores your files to that point. Hermes does not change what Claude Code's Read tool returns, so it cannot fix the agent's view. In a task with extra folders, that strip tracks changes in the main project only.
If this was useful
The stub was a tool-calling design choice: a tool result that pointed at another tool result and trusted it to still be there. The AI Agents Pocket Guide covers tool design, agent loops and the guardrails around them, which is where bugs like this one come from. It is one of 8 short books in Pocket Guides for Developers, on agents, RAG, prompts, observability, events, databases and system design. Hermes IDE is free, source-available under BSL 1.1, on macOS, Windows and Linux, and not affiliated with Anthropic: download it or read the source.

Top comments (0)