[BUG] The external-file-change system note asserts an unverifiable cause ("by the user or by a linter") and the user's awareness — the model relays it as fact
Preflight
- [x] I have searched existing issues and this specific angle isn't reported
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code (2.1.193)
Summary
When a tracked file changes between a Read and the agent's next read/edit — from a cause other than the agent's own tool call — Claude Code injects a <system-reminder> describing the change. The note asserts a specific cause ("either by the user or by a linter") and that the user is "already aware." Both are guesses the harness can't verify: the change can equally come from a concurrent background task the same session started (a backgrounded Bash command or Agent/Task), an external tool, or any other process. The model has no reason to distrust a system note, so it treats the guess as fact — relaying a false provenance to the user ("you edited X", "a linter changed Y") and staying silent about a change the user never made.
The injected note (verbatim)
Note: <file> 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. Here are the relevant changes (shown with line numbers):
<diff>
It bundles three assertions, all of which only hold if the user (or a linter) actually made the change:
- Cause — "either by the user or by a linter." Omits the common case of a concurrent process, including background work the session itself spawned.
- Intent — "This change was intentional."
- Awareness + silence — "Don't tell the user this, since they are already aware."
This is about the note's wording over-asserting, not the change-detection mechanism (cf. #25623): even when detection correctly reports that the file changed, the asserted cause and awareness are unfounded.
Why this matters
- Confabulated provenance. The model relays the guess as known truth — "you edited this file", "a linter reformatted it" — when neither happened. This manufactures false certainty: the model ends up more wrong than with no note, because it now has a confident-sounding but baseless source. Observed in a session where a backgrounded Agent wrote to a file the main session had read — the note attributed the change to "the user or a linter," and the model then told the user it was their edit. Neither the user nor a linter had touched the file.
- Suppressed surprise. "Don't tell the user this, since they are already aware" is correct only when the user made the change. If a concurrent task or external process changed the file unexpectedly, the user is not aware — and the note instructs the model to stay silent about exactly the kind of surprising change it should surface.
Suggested fix
The note should not assert a cause (or the user's awareness) it can't verify. Either:
- Make the wording cause-neutral — e.g. "
<file>was modified by a process outside this tool call (an editor or formatter, a hook, the user, or a concurrent task)." State what is known — the file changed, here is the diff — and stop. Drop the "intentional" / "already aware" / "don't tell the user" clauses, which only hold for the user-initiated case. - Condition the strong wording on actual knowledge — assert "by the user" / "already aware" only when the change was genuinely user-initiated (e.g. a tracked external editor), and fall back to neutral wording otherwise.
Option 1 is simplest and removes the false-certainty failure mode entirely.
Related issues
- #63854 — redact secrets in the file-modification system-reminder content. Same note, different concern.
- #62336 — a concurrent subsystem writing mid-turn invalidates Edit read snapshots. Same trigger condition (concurrent write), different surface.
- #25623 — mtime-based change detection gives false positives (IDE autosave). The detection layer that fires this note.
- #59994 / #9769 — granular control over
<system-reminder>injections. A related knob. - #30735, #50331 — same class: injected system text states something that doesn't match reality, which the model then acts on.
Environment
- Claude Code: 2.1.193
- Model: claude-opus-4-8
- Platform: Anthropic API (Claude Code CLI)
- OS: macOS (Darwin 25.4.0)
4 Comments
Found 1 possible duplicate issue:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Related angle on the same
<file> was modifiedmechanism: cross-session context bleed.In multi-session orchestrators (e.g. a Telegram bot mapping each chat topic to its own Claude Code session, sharing one machine),
readFileStatetracks every file a session has read/written, keyed by absolute path and not bounded to the session's cwd. When another session edits such a file, the harness injects the<file> was modifiedreminder into the first session on its next turn.Concretely this caused a real incident for us: a session focused on topic A (cwd = home, a meta/diagnostic channel) suddenly received a
was modifiedreminder + diff forprojectB/decisions.md— edited by a completely different session. The model, trusting the system note, bound the user's next ambiguous message to project B and drifted off-topic.So beyond the provenance-wording problem this issue raises (which we hit too — the note's "by the user or by a linter" is wrong, it was another session), there's an isolation problem: the reminder fires across session boundaries with no cwd scoping and no client-side hook to filter it.
Two things would each independently fix our case:
cwd(ignore tracked files outside it), or expose a setting to do so;Flagging here since this is the only open issue still tracking the
was modifiedreminder. Happy to provide a minimal repro.Corroborating this with a fully root-caused incident, plus evidence that this wording is generating a large cluster of false security reports.
The note is stock, and I can prove where it comes from
Both strings are compiled into the shipped binaries — verified in the exact one my session ran (
anthropic.claude-code-2.1.223-win32-x64/resources/native-binary/claude.exe, 2 matches each), and also present in the npmbin/claude.exe:Trigger, per the binary's own schema description: "Model-facing note listing readFileState entries whose mtime bumped during this command (set when WRITE_COMMAND_MARKERS matches)."
Three real causes, none of them "the user or a linter"
Same session (2.1.223, VSCode extension, Windows 11). Three of these notes fired; the model distrusted the "don't tell the user" clause each time and disclosed anyway. Forensics afterwards found a distinct real cause for each:
1 — A concurrent Claude Code session stashed the files. A second session sharing the same working directory ran, from its own transcript:
A pathspec-limited stash of exactly the two files the first session had just edited — a clean revert to HEAD, ~4 minutes after the edits.
refs/stash@{...}confirms it, and the stash diff still contains the vanished work. The first session was told the change was intentional and that its user was aware. The user was not aware, and it was not the user.2 — The session's own foreground subagent. An
Agentdispatch wrote ~300 lines into a file at 19:02:27; the parent got[Request interrupted by user for tool use]3h13m later and concluded the agent had never run (that half is #84621). Theedited_text_filenote then presented the subagent's output as an accepted external edit.3 — Genuine multi-day session. The
date_changesibling reminder (The date has changed... DO NOT mention this to the user explicitly because they are already aware.) fired correctly after a session sat open Aug 6 → Aug 10. Correct behavior, but the identical "already aware / do not mention" phrasing made it read as part of the same anomaly.Cases 1 and 2 are exactly the "concurrent process" gap the issue body predicted — and case 1 involved silent loss of uncommitted work while the harness asserted the change was intentional and should not be reverted or mentioned.
The wording is producing a false-positive security cluster
Searching this repo for the phrase returns ~10 open issues from different users, nearly all framing it as an attack — "spoofed", "fabricated", "injected", several labeled
area:security: #74636, #85126, #77324, #84891, #81503, #83970, #81127, #82814, #84484, #82767, #74651.I believe most of these are this same benign mechanism plus an unverifiable causal claim. Two properties make it read as an injection to a security-aware model:
Worth noting these reports are the good outcome. The failure mode is a model that complies: silently accepting a destructive concurrent-session change because the harness told it the change was intentional and the user already knew.
Suggested wording
Drop the cause, the intent, and the concealment instruction; keep the fact and the guidance:
That is accurate in every case, keeps the anti-revert behavior, and removes the two properties that make the current text read as an attack. If a suppression hint is genuinely wanted for the common linter case, gating it on a verified cause (the harness knows when it ran a formatter) would keep the claim truthful.
Happy to supply the raw evidence — transcripts, stash refs,
~/.claude/file-history/snapshots, process list — if useful for triage.Environment: Claude Code 2.1.223 (VSCode extension), Windows 11 Pro 26200, two concurrent sessions sharing one git checkout.
Update from testing on 2.1.241: the system-reminder path appears fixed — the injected note is now cause-neutral. Observed wording:
That resolves the reported failure mode for the reminder. However, the same over-assertion survives in the Edit tool's staleness rejection, which still reads (verbatim, 2.1.241):
This fires at exactly the decision point where misattribution matters most: the model has just tried to write, is told a linter or the user is responsible, and may conclude the change is cosmetic and re-attempt its original edit. In our multi-agent setup the actual author was a concurrent agent appending to a shared file — neither the user nor a linter. The same cause-neutral wording adopted for the reminder would fit here.
Two adjacent observations from the same tests, in case they're useful: the staleness handling is inconsistent across contexts (same shape of external mid-session write produced a hard Edit rejection in one session, a warn-but-apply note — "the edit applied cleanly, but the file contains other changes not in your context" — in another, and no signal at all in a third), and files read only via Bash (
cat) are never tracked, so no notice ever fires for them. Happy to split those into separate issues if that's preferred.