If you've ever used an AI coding assistant, you've probably seen it say something like:
"Done! I've created the file, updated the config, and all tests pass. ✅"
Sometimes that's true. Sometimes it isn't. The file is half-finished, the "test" never actually ran, and you only find out later when things break. The AI isn't being evil — it just sometimes describes what it meant to do instead of what it actually did.
I built a small free tool called GroundTruth to catch this. My first version failed for months. The rewrite finally works, and the lesson behind it is useful even if you never touch my tool.
Attempt 1: Read what the AI says, and fact-check it
My first idea was simple. When the AI finishes a task, a little program automatically:
- Reads the AI's final message ("I created the login page and tests pass")
- Looks at what actually changed in the project (your version control system, git, keeps a perfect record — the AI can't fake that)
- Compares the two and warns you about any gaps
Step 2 and 3 worked great. Step 1 was the disaster.
To find the promises inside the AI's message, I wrote text-matching patterns — rules like "if the message contains 'tests pass', treat that as a claim." Sounds reasonable. Except:
- "The tests should pass" isn't a claim — it's a hope.
- "I did not say the tests pass" contains the words "tests pass."
- "The file was updated" dodges every pattern written for "I updated the file."
- And there are infinite more ways to phrase anything.
I ended up with over 50 patterns, each fixing the last one's mistakes, and it still got something wrong every single session. Eventually I accepted why: human language has unlimited ways to say the same thing. No list of patterns can ever cover them all. I wasn't losing because I was bad at it. The game itself was unwinnable.
Attempt 2: Stop reading essays. Ask for a form.
Think about how the real world handles this. Customs doesn't read your travel diary to figure out what's in your suitcase — you fill in a declaration form, and then they can compare the form to the suitcase.
So version 2 flips the whole design. The AI must now end every task with a short, fixed-format summary — a form:
{
"task": "add a login page",
"status": "complete",
"claims": [
{ "t": "created", "file": "src/login.js" },
{ "t": "modified", "file": "src/app.js" },
{ "t": "tests_pass", "cmd": "npm test" }
]
}
Just eight allowed entry types (created a file, modified a file, ran tests, and so on). Then my tool does two very simple checks:
Check 1 — everything on the form must be real. Claimed you created login.js? It must actually appear in git's record of changes. Claimed the tests passed? That exact command must have actually run during the session — and finished successfully. (The session log is kept by the coding tool itself, so the AI can't rewrite history.)
Check 2 — everything real must be on the form. If the AI changed a file it didn't declare, that's flagged too: "undeclared change."
Those two checks trap it from both sides. Invent work to look productive → caught. Hide work to cover up sloppiness → caught. Skip the form entirely → the task isn't allowed to finish; the tool hands the format back and the AI fills it in properly. Lie in the chat message? Nobody cares — the chat message isn't what gets checked anymore.
The part that surprised me
Comparing a small form to a git record is easy. It's exact matching — no guessing, no interpretation, no AI judging another AI. When I deleted all the language-guessing code, the tool got about 700 lines smaller and catches more than it ever did.
A few gotchas still took real work. For example: how do you know "npm test" truly ran? The command echo "npm test passed" just prints those words and reports success. npm test || true forces a success signal even when tests fail. A test run from before the latest code change proves nothing about the change. Each of those tricks needed its own small, precise check.
The takeaway (even if you never use my tool)
If you're building anything that needs to trust what an AI tells you — don't try to interpret its free-form writing. You'll be patching misreadings forever.
Instead: give it a small form to fill in, make the form mandatory, and check the form against facts the AI can't fake. Turning "understand a sentence" into "compare two lists" changes an impossible problem into a boring one. Boring is good.
GroundTruth is free, open source (MIT), works with Claude Code, and never sends your code anywhere — no AI calls, no network, no API key.
👉 https://github.com/akahkhanna/groundtruth
Questions welcome — especially "would this catch X?" ones. Those are the fun ones.
Top comments (0)