Edit tool's cached snapshot not invalidated by Bash writes — silently reverts changes outside `old_string` window

Open 💬 0 comments Opened Jun 10, 2026 by cp-highnote

Summary

When a Bash tool call writes to a file (via python3, sed -i, cat >, tee, etc.), Claude Code's Edit tool does not invalidate its cached in-context snapshot of that file. The next Edit against the same file applies its old_stringnew_string substitution to the stale snapshot and writes the full result back to disk — silently reverting every region of the Bash write that lies outside the old_string window. There is no error, no warning, and no "file state is current in your context" notice on the Bash tool result that would let the user or agent know the snapshot is stale. The behavior is deterministic and reproducible in any session that interleaves Bash writes with subsequent Edits on the same path.

Reproduction Steps

  1. Read file F (e.g., a markdown file with two distinct sections, A and B) so the Edit tool caches a snapshot.
  2. Use Bash to mutate section A of F out-of-band — e.g., python3 strip_fields.py F, sed -i 's/foo/bar/' F, or cat > F.
  3. Observe the Bash tool result: it does not emit a "file state is current in your context" notice (that notice is emitted only by Edit / Write / MultiEdit).
  4. Issue an Edit against F targeting section B, with an old_string / new_string pair that does not overlap section A.
  5. Result: the Edit succeeds, but the file on disk now contains the pre-Bash version of section A plus the new B edit. The Bash write to A has been silently reverted.

Forensic Evidence

Captured from real Claude Code session <SESSION-ID-REDACTED> on 2026-06-09:

  • File-history snapshots at ~/.claude/file-history/<SESSION-ID-REDACTED>/5e7bf2243b6890f6@v{3,4,5}:
  • @v3 — file contains frontmatter fields node_type and originSessionId.
  • @v4 (20:06:58 PDT) — after python3 strip_fields.py Bash invocation, both fields are removed.
  • @v5 (20:23:06 PDT) — after a subsequent Edit on a different section of the same file (an unrelated body entry add at 20:09:43 PDT), both fields have re-appeared, even though no tool call wrote them.
  • Sub-agent cross-check: 180+ sub-agent JSONL transcripts in the relevant time window were stream-scanned for Edit / Write calls touching frontmatter — zero matches.
  • Hook cross-check: ~/.claude/settings.json declares 3 PostToolUse hooks; none read or write frontmatter.
  • Script / agent / skill cross-check: grep -rln "node_type\|originSessionId" ~/.claude/scripts/ ~/.claude/agents/ ~/.claude/skills/ returns zero matches.

The @v3 → @v4 → @v5 sequence is unambiguous: a field present at v3, removed at v4 by a Bash write, and present again at v5 after an Edit that did not target frontmatter — with no tool call in between that wrote those fields. The only mechanism consistent with the evidence is the Edit tool replaying its v3-era cached snapshot on top of the v4 on-disk state.

Why This Is a Platform-Level Bug (Not User Error)

  • The Bash tool result does not emit the "file state is current in your context" snapshot-invalidation notice. Only Edit / Write / MultiEdit emit it. There is no user-facing or agent-facing signal that a Bash write has invalidated the cached snapshot.
  • The Edit tool silently writes the full file back without diffing against on-disk state and without warning when the cached snapshot is older than the file's mtime.
  • This affects any workflow that interleaves Bash-side file mutations (sed -i, awk -i inplace, python3 script.py, shell redirects, tee, cat <<EOF >) with subsequent Edit calls on the same path — which is a common pattern for bulk maintenance scripts.

Suggested Fixes (Ranked)

  1. (Best) Invalidate the Edit snapshot on detected Bash writes. Have the Bash tool compare file mtime / digest of any path mentioned in its command before and after execution, and if any tracked file's mtime advanced, mark its Edit snapshot stale. The next Edit against a stale-snapshot path would then refuse to apply until a Read re-snapshots.
  2. (Medium) Emit the snapshot-invalidation notice on Bash writes. Append the existing "file state is current in your context"-style notice (or its inverse — "file state has changed externally; Read before Edit") to Bash tool results when a tracked file's mtime advanced. This at least gives the agent a chance to react.
  3. (Workaround documentation) Document the requirement in Claude Code's tool-usage docs: "After any Bash command that writes to a file Claude Code may later Edit, Read the file first to invalidate the cached snapshot." This is a workaround, not a fix — it relies on the agent remembering.

Affected Workflows (Real Examples)

  • Atlas / memory-system maintenance: Bash strip scripts that remove deprecated frontmatter fields across many files, followed by per-file Edit calls adding body entries. Strips silently revert.
  • Bulk frontmatter migrations via python3 migrate.py + per-file Edit cleanups in the same session.
  • In-place editing via sed -i / awk -i inplace followed by Edit-tool refinements.
  • Any maintenance script + per-file polish pattern where Bash and Edit alternate on the same path.

Severity

  • Silent data loss. No error, no warning, no indication to the user. The reverted state is reachable only via ~/.claude/file-history/ snapshot diffing.
  • Deterministic, not a race. The bug is purely a consequence of the snapshot-invalidation contract not covering Bash writes; it reproduces every time the pattern is followed.
  • Affects a core tool. Edit is among the most-used Claude Code tools, and Bash interleaving is a common pattern.

Currently Codified Workaround

The affected user's project memory now contains an L18 entry at ~/.claude/memory/_known_leak_patterns.md codifying the pattern, with a pre-fire main-loop reminder:

"After running any Bash command that writes to ~/.claude/memory/<file>.md (e.g., python3 ... > file, sed -i, cat > file), the next operation on that file MUST be Read to invalidate the Edit tool's cached snapshot. Failing to Read = silent revert of the Bash write."

Instance count on the discovery session: 4 files silently reverted (_known_leak_patterns.md, _doctrine_session_start.md, _placeholders.md, vault/extension-recipes/F1-add-vault-source.md).

This is a per-user workaround. The underlying platform behavior should be fixed at the tool layer so workflows do not depend on the agent remembering this invariant.

View original on GitHub ↗