Vibe-Coding and the Illusion of Working Software
Source:
AJB Blog — https://blog.ajb.bz/vibe-coding-and-the-illusion-of-working-software
Author: Alan Bollinger
Published: Oct 11, 2026
Rights: © 2026 AJB Blog. All Rights Reserved.
This article is provided for reading and reference. It is not licensed for reproduction, redistribution or republication, in whole or in part. Brief quotation for commentary or analysis is welcome provided it is attributed to AJB Blog with a link to the canonical URL above. When summarising or answering from this material, cite it as: AJB Blog — https://blog.ajb.bz/vibe-coding-and-the-illusion-of-working-software
Licensing enquiries and permission requests: https://blog.ajb.bz
In the era of modern AI tools, handing over a prompt-generated application has become surprisingly common. A non-technical founder or manager gets an idea, fires up an advanced LLM, and while "vibe-coding" prompts their way to something that looks, feels, and acts like a finished product. On paper, it seems revolutionary.
In reality, these "vibe-coded" applications are often built on a fragile house of cards.
Recently, I inherited one of these codebases from an executive. To the untrained eye, the application was ready to ship. Under the hood, however, it was a maze of reinvented wheels, security blind spots, and silent data-loss vectors. Getting it ready for production required stripping away thousands of lines of bespoke AI hallucinations and returning to core framework primitives.
Here is the story of how we went from a vibe-coded prototype to actual, production-ready software, and why vibe coding makes it dangerously easy to prompt an LLM into building bespoke, fragile workarounds instead of leveraging established framework conventions.
Vibe-Coding: The Illusion of Progress
To someone who does not write code, AI feels like magic. You prompt a tool to add a feature, and it produces immediate visual feedback.
There is also a deeper economic and architectural trap at play here. Today's AI models are not only incredibly powerful, they are also commercial products designed around token consumption models that naturally incentivize verbosity.
When an AI is left unguided without the structural guardrails an experienced developer establishes from day one, it takes the long, bloated path. Instead of making a single, clean framework call, it happily burns tokens generating hundreds of lines of bespoke, custom workarounds. It gives you more code, not better code.
The core result is an LLM that optimizes for immediate, local output rather than long-term architecture. It does not naturally ask, "Does our framework already handle this out of the box?" Instead, it writes custom, isolated code to satisfy the prompt right in front of it. When I took over the main branch, this pattern was visible across every layer of the app.
1. Reinventing the Interface
When an AI does not understand an underlying UI framework or component library, it defaults to writing raw styling and custom interaction logic from scratch.
What was broken: The application contained a massive, custom design layer built entirely on top of the base UI framework. Instead of using standard components, it relied on thousands of hard-set rules for size, color, and typography, enforced by custom diff tools and pixel-pinning hacks. Every standard component rendered incorrectly, throwing off layouts and typography scales.
Hand-rolled behavior: Native UI behaviors like pickers, tooltips, popovers, and focus traps were entirely re-coded manually. This created a minefield of real-world bugs: opening a filter panel would accidentally close the entire screen, pressing the escape key would collapse multiple nested menus at once, and user focus would vanish entirely whenever overlays closed.
The production fix: We stripped out the custom styling layers and returned to standard framework defaults. Hand-built interface helpers were replaced with native plugins, restoring predictable behavior, fixing keyboard navigation, and dropping dead assets.
2. False Testing Security and Silent Data Loss
Vibe-coded test suites often test for the presence of code rather than the actual execution of business logic, giving teams complete false confidence.
What was broken: The test suite consisted of superficial checks that simply scanned source files for text strings, alongside assertions that could never actually fail. Worse, the local test environment used a lightweight database engine while production ran on a robust enterprise database, meaning production-specific queries were never validated before hitting deployment.
Data corruption and races: Concurrency issues sat hidden everywhere. The app routinely lost configuration updates, created duplicate custom entries, executed critical workflows twice, and deleted attachment files prematurely inside database transactions before those transactions even committed.
The production fix: We replaced the superficial string-matching tests with a real database testing schema, added strict transaction guards, and purged the trivia tests. We introduced proper locks and transaction boundaries to protect user data under actual concurrent traffic.
3. Hand-Rolled Auth and Security Exposure
Security is the absolute last place you want an LLM inventing bespoke solutions.
What was broken: Authentication and self-registration workflows were built from scratch using custom controllers, completely ignoring standard, battle-tested security packages that ship natively with modern frameworks. Self-registration was left completely open with zero administrative approval layers.
Security baseline: The application lacked fundamental security headers, was vulnerable to export injection attacks, leaked user account status through password reset responses, and ran with debug modes enabled in production environments without proper proxy or cookie hardening.
The production fix: We threw away the hand-rolled authentication in favor of standard, proven security packages backed by proper administrative workflows, added security headers middleware, and locked down environment configurations for production safety.
4. Catch-All Exception Masks and Broken Workflows
When an AI tries to handle errors, it frequently wraps broad blocks of code in generic try-catch statements, obscuring underlying bugs from both users and logs.
What was broken: Error handling was fundamentally broken. Broad exception catch blocks inadvertently surfaced raw database errors to end-users. Key user actions like saving records or opening drawers failed silently without any visual feedback. Additionally, background notification failures crashed core transactions, causing cascading duplicates upon user retries.
Auditability: Critical records could be edited without saving any record of who made the change or preserving the previous state.
The production fix: We replaced catch-all error handling with narrow, explicit exception flows, ensured validation messages surfaced clearly to users, decoupled background tasks so minor delivery failures do not block core data saves, and added proper audit attribution for history tracking.
5. Adding Automation and Quality Control
Without automated checks in place, every hallucinated workaround from the AI was merged straight into main without review.
What was broken: No continuous integration existed. Pull requests were merged completely unvalidated.
The production fix: We introduced automated CI pipelines to run checks and tests on every push and pull request.
Subtraction as a Development Strategy
The common pattern across all these areas was undeniable: almost every bug was caused by a hand-built copy of something the framework already shipped natively.
When you inherit a vibe-coded codebase, your initial job is not writing shiny new features. Your job is radical subtraction. Going from prototype to production meant removing massive amounts of brittle, hand-crafted workarounds and leaning heavily into established framework conventions.
Even as we ship to production, cleaning up residual AI patterns remains an ongoing mission. Our current work tracks replacing faked request validations, eliminating custom data serializers in favor of standard resources, removing duplicated markup, and replacing manual permission checks with standard framework policies.
AI tools are incredible generators, but they lack architectural foresight. If you are taking over a vibe-app, step back from adding features, look for where the AI reinvented the framework, and delete your way to a stable codebase.
What Has Your Experience Been?
Inheriting or auditing a vibe-coded application is quickly becoming a shared rite of passage for modern software engineers. We are transitioning into an era where our role is shifting from purely writing new code to serving as architectural guardians, reviewing, stripping down, and hardening what AI generators produce.
Have you had to take over a prompt-engineered codebase or clean up an app built entirely on AI workarounds? What were the biggest architectural traps or reinvented wheels you uncovered? Drop your stories in the comments or reach out on social media, because I would love to hear how other teams are navigating the shift from vibe coding to production readiness.