I record demos of my own AI app builder. Last week one of them made the agent look incompetent, and chasing why turned into a lesson about diagnosing agents in general.
What it looked like
The agent wrote a file. Then it wrote the same file again. Then it said this:
"the writes report success but the files aren't there"
"Many files aren't persisting despite success reports — likely a parallel-write race condition. Let me write them one at a time"
And then it did exactly that, slowly, for most of the run — defending against a problem that did not exist.
My first theory, which was wrong
NoCoder has an approval gate: in review mode the agent stops before each file write and waits for you to approve the diff. My immediate theory was that gated writes were being queued — so the agent's next read hit the real filesystem, found nothing, and panicked.
It explains the symptom perfectly. It is also completely wrong.
The gate is blocking, not queueing. At the call site:
if (this.approvalGate.shouldGate(sessionId, name, args)) {
const decision = await this.requestChangeApproval(sessionId, name, args, podName);
if (!decision.allow) { /* returns a normal failed ToolResult */ }
}
// only now does the write reach the pod
The comment above it says the quiet part out loud: the gate sits before anything touches the pod, so the diff shown is exactly what gets written and a rejected change never reaches disk. The agent is parked on that await the entire time a request is pending. There is no window in which it can write, move on, and read a file that isn't there yet.
I found this by reading the call site. I would not have found it by reading the transcript, because the transcript supported my wrong theory beautifully.
The actual cause
A loop guard I wrote.
If the agent issues a second write to a path it already wrote, the guard assumes the model is spinning, skips the write, and feeds back: "You already wrote X. Do NOT rewrite it."
That is the right behaviour when the file is sitting on disk. It is the wrong behaviour when the file is gone — and it is often gone, because the agent's own cleanup of node_modules, a config regeneration, or a scaffold step removed it between the two attempts.
So the agent was told it had already written a file that did not exist. It checked. It found nothing. It concluded the workspace was broken — which, from where it was standing, was a reasonable inference. Everything downstream was careful reasoning from a false premise handed to it by my own guardrail.
No data was ever lost, incidentally. Every claimed write was on disk once approvals landed.
The fix
Make the guard check instead of assume. It now probes the workspace for the file and only skips the rewrite when the file is genuinely still present.
What I actually took from this
Two things.
Guardrails are part of the model's world. We spend a lot of effort on prompts and tools and comparatively little on what our safety rails say when they fire. This one lied — confidently, in the imperative — and the agent believed it, because why wouldn't it. An agent can only be as sane as the feedback it is given.
A transcript is evidence of symptoms, not of causes. The agent's own explanation ("parallel-write race condition") was wrong, my first explanation was wrong, and both were wrong in the same direction: blaming the layer we could see instead of the one we had to go read. If you are debugging an agent and your theory came from its output, go read the call site before you ship a fix.
If you want to watch the run this came from, it is here: https://youtu.be/2sR4H1q9QCQ — the model catching a different mistake of its own, a missing Tailwind plugin, is the more flattering part.
NoCoder is what I am building: an AI app builder that proposes every change as a diff you approve before it touches your code. https://nocoder.codes/?utm_source=devto&utm_campaign=guardrail
Top comments (0)