AI coding agents hallucinate code, here's how to catch it
The function looked right.
user.getDisplayName() (sensible name, right object, exactly the method a user
model ought to have). The agent called it in four places, wrote a test around it,
and explained how it handles the null case.
It doesn't exist. It never did. Your model has a plain displayName property and
always has.
That's a hallucination, and it isn't a rare glitch you can wait out. It falls straight out of how these tools work, so the fix isn't a better model. It's a better loop.
Agents predict plausible code, they don't look it up
A coding agent generates the most likely continuation of what it has read. Most
of the time the most likely code is the correct code, which is why any of this
works. But when the agent hasn't actually read your definition of User, "most
likely" quietly becomes "most typical," an average of every user model it ever
saw.
The result is code shaped exactly like the truth. Right naming convention, right argument order, right idiom for your framework. Plausible in every respect except its existence.
That plausibility is the problem. A wrong answer that looks wrong costs you thirty seconds. A wrong answer that looks right gets merged.
The four you'll actually meet
Phantom methods and flags. A helper that ought to exist, a CLI flag that would be useful, an options object with a key nobody implemented. The most common and the easiest, because it usually fails to compile.
Version drift. The nastiest one. The API is real and the docs are real, but they describe v2 and you're on v4. Training data doesn't arrive with version boundaries stamped on it, so an agent blends three major releases into one confident answer. This code often runs. It just behaves subtly differently from what you think you shipped.
Invented config. An env var, a YAML key, a compiler option. Config is the perfect hiding place: nothing validates it, nothing errors, and the setting you carefully added does nothing at all for eight months.
Packages that don't exist. The agent imports something with a perfectly reasonable name that was never published. Irritating on its own, and a real supply-chain hazard, because attackers watch for commonly hallucinated package names and register them. If your install step ever runs unattended, treat a surprise dependency as a security event rather than a typo. More on that in keeping AI-generated code from shipping security holes.
Give the agent sources, not memory
All four have the same root cause: the agent answered from memory when it should have looked something up. So make looking things up the cheap path.
Point it at the code before it writes. "Read app/Models/User.php first,
then tell me which method to use" costs seconds and removes an entire class of
error. Types, schemas and interfaces are your ground truth: make it read them.
Pin your versions where it will see them. An AGENTS.md file naming your framework version, package manager and house conventions kills most version drift before it starts. The agent no longer has to guess which era of the library you're living in.
Wire in real sources. MCP servers let an agent query your live database schema, pull current library docs or read your issue tracker instead of recalling them. Reading beats remembering, every time.
Ask for citations. Make "quote the file and line, or the doc page, that shows this exists" a standing rule. It turns a confident invention into an honest "I couldn't find it", the answer you actually wanted.
Make the code prove itself
Sources reduce hallucinations. Execution catches the rest.
Anything that runs the code (a compiler, a type checker, a linter with an unresolved-import rule, a test) is a hallucination detector you already own. The highest-leverage rule in agent work is that the agent runs it before reporting done, and pastes the output. An agent that only writes code can't be corrected. One that runs it corrects itself in a loop you never have to watch.
Two habits close the remaining gaps:
- Diff your lockfile every time. A dependency you didn't ask for? Look it up manually before it installs anywhere.
- Grep the diff for the symbols it introduced. Boring, fast, and the only thing that catches invented config keys, which no test will ever touch.
Then read the diff like a reviewer rather than a spectator. That's the review workflow doing its real job: verification, not vibes.
Where this gets easier
Verification is a loop: read the source, write, run it, check the diff. And loops fall apart when they're spread across six terminal tabs and your memory of which you already checked.
That's the shape SanuDesk is built for. It's a desktop app that tiles your Claude Code, Codex and Gemini sessions into one grid, with a Kanban board the agents pull work from. Write "cite the definition you're calling" and "paste the test output" into a card's acceptance criteria once, and every agent that picks it up works to that standard, instead of you retyping the rule at 11pm and forgetting it where it mattered.
Loops cover the part nobody does by hand: a scheduled run that checks the lockfile for unapproved dependencies, re-runs the suite against current library versions, and opens a card when something drifted. It drives the model subscriptions you already pay for, and the free tier doesn't need an account with us.
The tell
Fluent, specific, no citation. That combination is worth a second look every time, because a hallucinating agent sounds exactly like a correct one. That's the whole difficulty.
You don't need agents to stop guessing. You need a loop where a guess can't survive long enough to reach your main branch.
Download SanuDesk free and point a workspace at your repo, or see how the grid, the board and Loops fit together on the features page.