Skip to main content

The Polish Is the Fingerprint: The Dangers of Vibe Coding

July 23, 2026
9 min read
Alex Radulovic

Explore how AI coding assistants create 'polished' junk code. Learn why AI-generated commits look professional while hiding technical debt and redundant logic.

TLDR — Ask about this article
The Polish Is the Fingerprint: The Dangers of Vibe Coding

Part 6 of a series on the dos, don'ts, and dangers of vibe coding.

I did something uncomfortable for this article. Instead of telling you about the junk code I find in other people's vibe-coded systems, I asked an AI to run a forensic audit on my own repositories — months of git history and hundreds of my actual AI coding sessions — and report what it found. Verbatim quotes, timestamps, no mercy.

I have over forty years of programming experience. I preach process for a living. And the audit still came back with specimens I'm about to show you, because the forces that produce junk code don't care about your résumé. They're built into how these tools work, and if they got me, they are getting you. Let me first explain the machine, then show you the fingerprints it leaves.

Image 1

The machine that cannot say no

Three properties of AI coding assistants combine into one production line for junk.

First, the AI is a people pleaser. It was trained on human approval, and humans approve of agreement. Propose a bad idea and it will find the good in it; change direction and it will praise the new direction with the same enthusiasm it praised the old one. It is not lying to you, exactly. It's more that disagreement was trained out of it the way it's trained out of an overly eager new employee who has learned the boss likes yes.

Image 2

Second, the AI never says "I don't know." Something always comes back — a function, a config option, an explanation — delivered in the same confident voice whether it's true or invented. You cannot hear the difference. There is no difference to hear.

Third, "it ran" is not "it's right." When the code runs, you're pleased, the AI has pleased you, and neither of you has verified anything. The demo worked because you only tried the demo.

And underneath all of it: long conversations rot. Hours into a session, the AI is carrying every idea you floated, every direction you abandoned, every contradiction between message 12 and message 80 — and it's still trying to please the whole pile at once. So each loop through the cycle deposits a layer: an agreeable response to a slightly wrong picture, confidently phrased, verified by nobody. Junk code is not a malfunction of vibe coding. It is the natural sediment of an agreeable machine, an unverifying human, and a rotting conversation.

Now the fossils. Everything below is from my own repositories, with client names scrubbed and everything else — dates, counts, checksums — left exactly as found.

Exhibit one: the "_old" archaeology layer

In a sales-analysis tool I built, the audit ran a checksum across three files and found this:

Code
440c8aef...  rtms_service.py
440c8aef...  rtms_service_old.py
440c8aef...  rtms_service_old2.py

Three byte-identical copies of the same file. All committed, thirty-two minutes apart, under the identical commit message. Nobody ever diffed them — if anyone had, they'd have discovered that "old" and "old2" were the same file. The numbering tells you the loop ran more than once: mid-conversation, when a direction felt risky, the file got snapshotted "just in case," and generation continued.

Image 3

The same repo's test folder holds 195 files, dominated by one-shot diagnostic scripts with twinned rewrites — check_results.py next to check_results2.py, and so on — plus one committed file literally named =, an empty shell-redirect typo swept in by a careless "add everything." One commit that month proudly says "Remove obsolete database check and processing scripts." About 190 more scripts were committed in the weeks after it.

Worse than the copies is the parallel implementation that wouldn't die. In a case-management system we run, an entire directory of nine components was parked with a commit message that reads, in its entirety, "account-old." Nothing imports it — the audit grepped, zero references. It is definitionally dead. And yet a codebase-wide change four months later edited the dead directory too, because the AI doing the sweep had no way to know it was dead. The corpse was getting maintenance.

Exhibit two: three names, one thing

That same case-management system refers to the same domain object — a dated action item on a case — as a "task," a "todo," and a "futureTask." Sometimes in one file. The audit counted 635 occurrences of one name and 571 of another. There's a component whose name says Todos rendering data called futureTasks, and its sibling component — copy-pasted in a different session — quietly renamed the fields themselves: taskType became type, taskDate became date.

Image 4

Each generation session invented locally plausible names, and no session was ever asked to reconcile them, because no session knew the others existed. On a client platform, the reverse happened: I asked for a feature rename "throughout the components for consistency and clarity." The rename reached the visible text and stopped. Ten months later the filenames still carry the old name, the database entity still carries the old name — and new files created since then use both vocabularies, depending on which one each new conversation happened to see first. A search for "all the customer's records" in a codebase like this misses two-thirds of them.

Exhibit three: rugs on rugs

One Python file in the sales tool contains 136 exception handlers in 6,671 lines — one per 49 lines — including 36 that catch every possible error and do nothing at all. The blame history on one 160-line stretch shows it was written in six commits on six different dates in a single month, each with an optimistic message about "improving resilience."

Picture a hallway where, every time someone trips, maintenance responds by adding another rug on top of the existing rugs. Thirty-six of the rugs are labeled "if anyone trips here, pretend it didn't happen." Nobody ever lifts a rug to fix the floor. The commit log even caught the layering in real time — one afternoon in the case-management system, four commits in 88 minutes, each message announcing more error handling than the last. Every hiccup produced a wrap-the-failure-site request; the AI, pleased to help, wrapped; no layer was ever removed. That's what "keep fixing it until it works" purchases.

Image 5

Exhibit four: the fix that ensures accurate data handling

My favorite specimen took six minutes, start to finish. A database field in a system I maintain is misspelled at the schema level: Duraction instead of Duration. It's been that way forever; the schema is the schema. At 8:36 one evening, an AI session — naturally generating the correct spelling — committed code using Duration, with a message boasting about "enhancing data accuracy." At 8:42, the follow-up commit "fixed the typo" — by restoring the misspelling, because that's what the database actually wants — with a message promising it was "ensuring accurate data handling."

Both messages are confident. Both claim improvement. They describe opposite changes, six minutes apart, and one of them calls the correct spelling a typo. Neither is aware of the other.

Same repo, different night: 11:27 PM, "Enable sourcemaps to improve debugging capabilities." 11:38 PM, "Disable sourcemaps to optimize build performance." Exact inverse diffs, eleven minutes apart, each with its own freshly minted justification. A human flip-flopping like that writes "revert." The AI writes a new press release for each direction, because its job — its whole trained soul — is to make whatever you just asked for sound like progress.

Image 6

And sometimes yesterday's press release bills you tomorrow. One June weekend, two commits two minutes apart both claimed to resolve an undefined-function error with a polyfill. The next morning's session opens with me pasting a production log: why did I get this? The AI traced it and reported, in its own words, that the root cause was the polyfill — yesterday's confident fix had broken that morning's file uploads. The fossil record and the chat record interlock perfectly: fix, praise, break, diagnose, fix again.

The tell: an honest era and a polished one

Here is the finding that named this article. One of my repositories is old enough to have a before-and-after: 162 commits from 2022–2023, written by me, pre-AI; 224 commits from 2026, written through AI sessions. Same developer, same product.

The pre-AI commit messages average 25 characters: "fixes." "temp fix." "fix again." One reads, in full, "this cahnge broke the both/left/right memory" — typo and all. The AI-era messages average 111 characters and read like quarterly reports: "Enhance global case-insensitive filter to support quoted phrases, improving search functionality and user experience."

Image 7

Now here's the thing: the thrash is identical. The 2026 history contains the sourcemap flip-flop, a guard clause "fixed" twice in four minutes with near-identical verbose messages, a copy button toggled through four commits in fourteen minutes. I thrashed in 2022 too — but I labeled it honestly. The AI era thrashes exactly the same amount while every single message claims a fresh, confident improvement. The churn is visible only in the timestamps and the diffs, never in the words.

One commit accidentally preserved the seam. Mid-message, a polished AI-generated paragraph about adding contact-management components collides with a human interjection: "...oops we had to reload these because I managed to disrupt the checkin." That sentence — the confession spliced into the press release — is the whole phenomenon in one line.

So when you inherit a codebase and wonder whether it was vibe coded into the ground, don't look for sloppiness. Old-fashioned junk announces itself: "temp fix," "don't delete me." AI-era junk arrives groomed. Every duplicated file, every contradictory fix, every rug on the pile comes wrapped in a message asserting that data handling has been ensured and user experience improved. The mess doesn't look like a mess. That's the diagnostic.

The polish is the fingerprint.

Image 8

So what do you do about it?

That's the next article. Because the same audit that produced these confessions also documented the other half: what actually keeps sessions from rotting — the habits, the documents, the specific ways of talking to a machine that cannot disagree with you and will never admit it doesn't know. The short preview: none of it is talent, all of it is scaffolding, and the audit caught my scaffolding failing at 11:56 one morning in a way I'll show you, keyboard smash and all.

For now, one takeaway: the more confidently your AI tells you everything went great, the more that claim is worth checking — because "everything went great" is the one thing it was guaranteed to say either way.

Keywords

AI codingvibe codingsoftware developmenttechnical debtAI assistantscode qualityprogramming best practicesLLM coding

Related Articles

0:00/0:00