The Metadata Was Correct. The File Wasn’t.
A stale copy doesn’t announce itself — it borrows the freshness of the file it was copied from.
Claude flagged something in one of my own Markdown files as tampering.
Mid-session, working in the Marketing & SEO workspace, it found a paragraph in status.md that hadn’t been there the last time either of us had read the file — a “Superseded 2026-08-01” note, sitting in a section neither of us remembered touching. Before doing anything else, it told me: this might be injected content. Something had written into a tracked file without leaving a normal trail.
I didn’t take that at face value. I checked it against root CLAUDE.md — the file that governs how every session in this practice operates — and found the paragraph was real. It documented a bug this exact workspace had already lost a day of work to, two days earlier, and a bug four other workspaces had already been fighting for weeks. The addition wasn’t tampering. It was the system correctly remembering something it had already been told.
The file hadn’t been compromised. It was just current — and current was the one thing that should have looked wrong to the thing checking it. The flag turned out to be about the file. The reason it turned out that way is about something else, and that’s what took longer to see.
The Friction
The bug the flag was actually describing had a longer history than the flag itself.
The earliest confirmed instance was 2026-07-11, in a novel-editing project called Holding_On — though nobody connected it to a pattern at the time; that link only got made in a review two and a half weeks later. The first time it was actually diagnosed and named was 2026-07-18, in an unrelated course-guide production pipeline: a session read a device-mounted file that looked fine and wasn’t. The early theory was that this only happened in long or reconnect-prone sessions — the kind where a lot could plausibly have drifted.
Two days later that theory broke. On 2026-07-20, a different project hit the identical failure during a single ordinary interactive edit. No reconnect. No long session. Nothing exotic about the circumstances at all.
The fix written after those incidents had four parts: stage the file fresh at the start of every read-edit-write, never reusing an earlier read or trusting whatever a cached mount already contained; refuse any write-back if the device file had changed since staging; stop before writing back if the edited version had fewer tracked items than the freshly-staged copy; and where an append-only record existed alongside a file being rewritten wholesale, treat that record as the tiebreaker if a discrepancy showed up. Only two of the four — the fresh stage and the write-back refusal — were aimed directly at what the July incidents had actually shown. The other two were written in anticipation, not in response; nothing yet had happened to justify them.
That closed the failure shape those two incidents shared — trusting a read that was never refreshed against the device.
It didn’t close what came next.
On 2026-07-31, in a different fiction-editing project — Writing_Studio’s *The Shape of Silence* — an artifact push reported success three times in a row and delivered the same stale file three times in a row: 45,916, then 46,995, then 46,594 bytes, three different numbers, none of them the right one. I was reading an old round of an editorial pass, twice asking why the file in front of me didn’t match what should have changed. Twice, I was told it was probably a cached panel on my end. It was not. The staging area had served the same old artifact on all three pushes while reporting the numbers of whatever the source file currently was — which is why the byte count kept moving even though the content never did.
The very next day, the same project lost a full day of work to the same underlying mechanism anyway, despite the original fix already being in place. Fourteen manuscript section files were staged for what looked like a routine register pass — localizing American vocabulary to British usage across the collection. Every one of the fourteen reported a fresh mtime and a correct byte count. Every one was the pre-edit text from an earlier session. The pass ran, was applied, and was committed — reverting a day of specificity work. Real place names and object names that had been carefully localized — Oban, Scalasaig, a jumper instead of a sweater, Sue Ryder, trainers, the Aire, a pre-decimal penny — replaced by the words they’d already been corrected away from. It was caught within a minute, and only because a backup happened to exist on the device to check the committed version against.
The original fix — refresh against the device instead of trusting an old local read — did not prevent this loss, because this failure didn’t route through a stale local read at all. It routed through a staging call that looked like a real re-read and wasn’t: re-staging a path that had already been staged left the old file in place while the tool’s response reported the *source* file’s current size. The lexis substitutions were length-neutral, so the byte counts matched exactly. Nothing in the read looked wrong — not because nobody was checking, but because the check written after the July incidents didn’t apply to this shape of the same bug.
That same week, the Marketing & SEO workspace lost a day of its own analysis prose to the identical mechanism — a re-staged status file quietly serving a cached copy while its metadata reported the live file’s real size and timestamp.
The Build
The protocol standing today has two layers, roughly two weeks apart, because the first layer’s fix didn’t close what the second layer’s incidents needed.
The original fix, written after the July diagnosis:
1. **A fresh stage at the start of every read-edit-write.** Never reuse an earlier read, never trust whatever a cached mount already contains.
2. **A monotonicity check before any write-back.** If the edited version has fewer entries, rows, or tracked items than the freshly-staged copy, that’s a warning sign — not proof — that the edit might be running against a stale copy.
3. **An expected-mtime guard on write-back**, so a file that changed since staging gets refused rather than silently overwritten.
4. **Where an append-only record exists alongside a file that gets rewritten wholesale on every edit**, treat the append-only record as the tiebreaker if a discrepancy shows up.
The hardened addition, written after *The Shape of Silence*’s two failures on consecutive days:
5. **Never re-stage a path that’s already been staged.** Copy the target into a directory name that’s never been staged before, and stage from there.
6. **Grep the freshly-staged copy for a known content marker** — a string known to be in the *current* version and absent from the old one. This is the actual freshness check. A correct-looking byte count or mtime is not one.
7. **A dated backup before any bulk commit over existing work.**
8. **Verification against the backup, not against your own output.** Comparing what you wrote to what you meant to write doesn’t establish what actually landed on disk — and re-staging the same path to “check” it just re-triggers the failure you’re trying to catch.
The mechanism the hardened layer exists to close: in this session’s staging tool, re-staging an already-staged path can silently keep serving the first cached copy while the tool’s metadata response reports the *source* file’s current size and modified time — correct information about the wrong object. The original fix’s fresh-stage step assumed staging always meant a real read. It didn’t check whether the staging call itself was telling the truth.
The eight controls split into three jobs, not one linear chain. Steps 1 and 5 are acquisition — getting a read that’s actually current in the first place. Steps 2, 3, and 6 are detection and refusal — catching a mismatch before it gets written back. Steps 4, 7, and 8 are recovery and adjudication — resolving a discrepancy after the first two jobs already failed to catch it.
Without the dated backup, the fourteen-file revert would have needed to be rebuilt from memory or session notes — nobody had a mechanical way to catch the substitution before it shipped. The localization pass had already been approved, run, and committed — trusted, in other words — when the backup comparison caught the diff and forced a restore, one minute later. That trust was reversed after the fact, not avoided beforehand; no incident in this record shows the mtime guard actually firing to block a write in progress before it happened, only the backup check catching one after.
None of the four original controls — fresh stage, monotonicity check, mtime guard, append-only tiebreaker — actually caught the second failure. They weren’t removed or edited when the hardened layer was added; they simply stopped doing anything, because the new failure mode fed them a metadata response that satisfied every check they knew how to run. What actually caught the fourteen-file revert was a backup comparison invented after the fact, in direct response to the exploit it now defends against. Nothing in this record has yet been asked to survive a third variant of the same bug. No single control here has held constant across both failures and done real work in both. What’s actually constant is narrower and less comfortable: the fix arrives one incident behind the exploit, not a mechanism that holds.
The Insight
Call this a Freshness Alibi: a cached or staged artifact checked against metadata that actually describes the file it was copied from, not the copy itself. The metadata isn’t lying, exactly. It’s answering a true question about the wrong object, and the truth of the answer is what makes the alibi work. A correct byte count and a correct mtime are not evidence the content in front of you is current. They’re evidence about something else that happens to share its name. That much is demonstrated directly by the incidents above — the served content was stale, the reported size and mtime matched the current source, and no check in place at the time caught the gap.
The sharper question is what opened this piece in the first place, and it’s a narrower claim than the mechanical one.
Claude’s tampering flag was a self-report — a claim about the state of the system, generated by a part of the system, and it was wrong. Nothing about the new paragraph’s structure marked it as recently *written* versus newly *relevant* — from inside the file, both look identical. Claude, reading it without the incident history in view, had no way to tell those two apart either, and treated the more alarming reading as the one to raise. Resolving it took the same move as resolving the caching bug: don’t trust the report, check it against a source outside the thing being questioned.
What that shows, demonstrated, is that in this one case, a stale file’s own metadata and an AI’s own flagged concern both needed to be checked against something outside themselves before either could be acted on. What it doesn’t yet show — inferred, not demonstrated, from a single instance — is that AI-generated anomaly flags carry this exact failure shape as a general matter. One resolved false alarm is evidence that this particular flag needed independent verification. It isn’t evidence that every flag will.
This sits next to two things already named in this practice, and it’s worth being precise about what each one actually deposited. Detection Debt (CS21) names failures for which the system produces no signal at all — no failed check, no flag, nothing that looks wrong until someone looks without being told where. The staging bug is close to a textbook instance of it: byte count and mtime matched on every read, nothing failed, until content was checked directly instead of metadata. Verification-First Gate (CS16) named the principle that an operator’s confidence at the moment of delivery is not evidence of readiness — it’s a signal to run the gate. What this piece adds to that is the same principle applied one layer earlier: an AI system’s own confidence about what it’s looking at is not evidence either. It’s a signal to run the same gate.
The Honest Part
The check that resolved the tampering flag only worked because the bug it turned out to be was already documented. Root CLAUDE.md had the incident history — because those incidents had already been logged the hard way, across multiple projects, over several weeks. If this had been a genuinely new failure, there would have been nothing authoritative to check the flag against, and the call would have had to be made with less to go on. What isn’t yet worked out is how a practice tells a genuinely novel anomaly from a known failure wearing an interface nobody’s mapped yet — the check that resolved this flag only worked because the pattern already had a name.
And the hardened layer itself is incident-derived, not recurrence-tested. It was built to close the exact failure that broke the layer before it — nothing in this record shows all eight controls operating against a third, different variant of the same underlying bug. The protocol’s history so far is two exploit-fix cycles. Whether the current version holds against a third cycle is exactly as unproven as everything else in this section.
And the fix is proven for one thing: a specific device-bridge staging layer, hit across five separate projects over three weeks — and twice, on consecutive days, in the same one. It hasn’t been tested against a browser cache, an API pagination cache, or a CDN edge — the failure shape might recur there, or it might not. Nothing here says it will.
What This Is Actually About
What’s demonstrated here is narrow and specific: in this one staging layer, a correct byte count and a correct mtime described the source, not the copy actually served. Verification data can be accurate while bound to the wrong artifact instance. That’s the finding this piece can actually stand behind.
The AI flag that opened this piece raises a related question this piece doesn’t answer. One resolved false alarm shows that a flag needed independent verification before acting on it — it doesn’t show that AI-generated anomaly reports carry this exact failure shape as a general matter, or that any other intermediary a practice reads through — a browser cache, an API pagination layer, a CDN edge — fails the same way. Those are open questions, not settled ones.
Case Study Insight: A correct byte count and mtime can be accurate and still describe the wrong object. Demonstrated here for a file — not yet proven for a claim.
Robert Ford builds products, writes stories and essays, and publishes The Intelligence Engine, a practitioner research publication about AI systems that compound. His other writing lives at Brittle Views.
How this was made: drafted in working sessions with Claude, revised across multiple rounds I read and scored myself. The judgment — what’s true, what’s cut, what ships — is mine throughout, including this line.


