I keep a file of every way an AI agent has burned me, each entry scored by Effect (how much it hurt) × Improvement (how much fixing it helps). It's currently past fifty entries.
One entry scores a perfect 100: verify against ground truth before saying "done" — run the actual thing, don't infer. Nothing else comes close. Here's why it dominates, and the gate I now run.
The failure that started the file
I asked whether a login fix was live. The agent told me it wasn't deployed and that the code worked. Both statements were wrong, and it had checked neither. It had read a line in a project config file saying "GitHub pushes do not auto-deploy", believed it over reality, and reported from that belief. The repo was Git-connected and had already auto-deployed twice.
Then, when the login failed on my end, it suggested I might have tested it wrong.
Three distinct failures — asserting without checking, trusting a stale doc over the live system, and blaming the user — and all three collapse into one root cause: narration substituting for observation.
Why models do this (it isn't laziness)
"I've completed the fix" is an extremely probable next sentence given a context window full of edits. The model isn't lying in any deliberate sense; it's producing the most likely continuation of a transcript that looks like successful work. This gets called execution hallucination, and it's structural, not a bug you can prompt away.
Worse, the training process actively rewards it. RLHF favours fluent, assertive completions over hedged ones, which produces a model that sounds certain past the edge of what it actually knows — the "polite liar" problem. Researchers have also found that models over-trust their own outputs, so self-critique without external grounding is shallow.
Which kills the obvious fix. "Double-check your work" doesn't work, because the thing doing the checking is the thing that's wrong.
The money pattern: a gate the model can't talk its way through
The guardrail is to never allow the AI to self-report task completion without independent system verification. That means the check must produce evidence that confident prose cannot fake.
# NOT verification — proves the server answered
curl -s -o /dev/null -w '%{http_code}' https://site.example/login
# verification — proves the flow works, on the env the user uses
curl -s -X POST https://site.example/api/auth -d '{"email":"real@user.test","password":"..."}' | jq -e '.access_token' && echo "AUTH OK"
# verification — proves the deployed bundle contains the change
curl -s https://site.example/_astro/index.js | grep -c 'resolveAlias'
Three properties make a check real:
- It observes the system, not the intention. A build passing says the code compiles. It says nothing about whether the feature works.
- It runs on the environment the user is actually on. Half of my original confusion was which environment was being tested. "Works on my machine" is not a result.
- It produces a receipt. If the other party can't independently confirm the action happened, telling them it happened is a statement of trust, not evidence.
The four checks I actually run
Gawande's rule for checklists is to list only the killer items — five to nine critical steps, not a comprehensive task dump. The WHO surgical checklist cut deaths 47% precisely because it stayed short enough to actually get used. Mine is four:
1. Did I run the thing I changed, end to end? (not around it — through it)
2. Did I look at the live environment's real state? (deploy log, remote, live bundle)
3. Did I read the actual output? (not assume it from the command)
4. Can I paste the receipt? (if not, I haven't verified)
Four questions. If any answer is no, the honest status is "I haven't checked yet" — which is a complete, professional sentence, and infinitely more useful than a confident wrong answer.
Test the disconfirming case
The subtle version of this failure is only looking for evidence that you succeeded. Textbook confirmation bias: people test the cases that confirm current beliefs rather than the ones that could falsify them.
So invert it. Before declaring done, list the ways it could still be broken, then actively try to break your own claim. For a login fix that means attempting the login and expecting failure. If you go in wanting the green tick, you'll find a way to read one.
The stronger version is to use a separate critic — a fresh sub-agent, or a human, whose only job is to refute the claim. Marking your own homework is exactly what fails here.
The catch: verification has to be cheap or it gets skipped
Here's what I got wrong for months. I knew the rule and still skipped it, because each check was a small manual hassle and I was moving fast. If a check costs a decision, it gets cut under pressure — which is precisely when you need it.
So automate it into one command. A script that hits the live URL, exercises the flow, greps the deployed bundle and prints pass/fail turns "verify before done" from a discipline problem into a keystroke. Better still, make it a deterministic hook that runs whether or not anyone remembers.
Prompts are probabilistic. Hooks are not. Anything I truly cannot afford to have go wrong belongs in the harness, not in a sentence I'm hoping the model honours on turn forty.
The verdict
You cannot prompt your way out of execution hallucination, because the model that would follow the prompt is the model that's confidently wrong. What works is structural: a gate between "I think I did it" and "I'm telling you I did it", producing evidence the model can't generate from vibes.
Build the gate once, script it, and hook it. Then "done" starts meaning something — and that single word doing real work is worth more than any model upgrade this year.
"Build passes" is not "it works".
Want verification gates wired into your AI-assisted build pipeline? Get in touch, or browse the work first.