It Rewrote a File It Had Never Opened
The builder I wrote about last week, the loop that improves a repository while I am away, chooses what to change by reading three things: the package manifest, the backlog, and twenty sampled file paths. Not file contents. Paths. Every model call it made ran with no tools at all, so the analysis pass had never been able to open a file, and the prompt it answered demanded, for each file it proposed to change, the complete new contents of that file.
It complied. It had to; that was the schema. So the loop’s first stage was a model composing whole-file rewrites of code it had never seen, from the file’s name and a description of the task.
Why it never blew up
A grounding pass sits between the analysis and the disk. It takes each proposed change, reads the real file, and refuses to write anything it cannot match against the real contents. That pass is why the loop never wrote garbage. It is also why the loop mostly did nothing: a rewrite composed from a filename rarely matches the file, so the usual end of a run was the line “no usable edits came back”. Safe, and accurate about nothing.
Then the part I had not noticed. The grounding pass’s request has fields for the path, the current contents, a brief description of the change, and a rationale. It has no field for the rewrite. The whole-file code the analysis pass had spent its entire output budget composing was discarded on arrival, and a second model call re-derived the edit from the description field, which the prompt itself calls a brief description. The engine paid for the file twice, and the edit that finally reached the disk was written by a model working from a one-line summary of a decision another model had made without the file open.
I found all of this by reading, not by incident. There is no run on record where it bit, and there could not be: the failure mode was to produce nothing, and producing nothing was not logged as a failure. It was logged as a completed run with no edits.
Reading first, then edits
The analysis call now runs with exactly three tools, all read-only, confined to the target directory, in a mode where no write or shell tool exists at all. The prompt tells the model to open every file it intends to change before proposing a change to it, and gives the read loop ten minutes instead of the old budget.
And it no longer answers with files. It answers in the grammar the grounding pass already speaks: exact find-and-replace pairs, copied out of the file it just read. The grounding pass applies them directly, falls back to the second call only for a file whose edits do not match, and refuses what still does not. On a 51,822-byte file, the same change became a 692-byte response instead of a full restatement, which is the difference between a file that size being editable and a guaranteed truncation.
Two things that switch made fatal, and that had been quietly wrong all along. The analysis pass’s JSON extractor counted brace depth without tracking string literals, so a response carrying an unbalanced brace inside a find string returned “no JSON found” and the run no-oped. And the discovery prompt restated its own schema demanding whole-file code after the analyzer prompt had asked for edits, so the unattended mode would have kept sending files forever. Both fixed; both would have been invisible for as long as the loop kept producing nothing.
The green checkmark
There is a third stage, and it was the worst. The single-shot improve command, the one a person types, wrote the model’s edits and printed “Updates applied successfully!” without checking that the repository still compiled or that its tests still passed. The unattended mode refused to even start in a repository with no validation signal, on the stated grounds that it would otherwise commit changes it cannot verify. The interactive mode, with a human right there to be misled, had no such rule.
It now measures the repository before writing, snapshots the exact bytes of every file it is about to touch, applies, and measures again. A change that introduces type errors, new failures, or fewer passes than the baseline is undone as it was found, files restored, created files removed, every reason printed. A change that merely fails to score as an improvement is kept, because the caller asked for that change and only a regression justifies undoing it. And the typecheck result had been cached for five seconds with no file-change key, so an apply and a re-measure inside that window scored the change against the baseline’s own typecheck and could never see an error it introduced. That one also fixed the unattended loop.
An unread output
The line I keep from this is short. The most expensive stage in the loop was the one whose output nothing consumed, and the only thing an unread output can do is fail. It cannot be wrong in a way anyone sees, because nobody sees it. It cannot be right in a way that helps, for the same reason. It can only cost, and the cost was the one column of the run I was not watching.