← All articles
App Development IT Leaders Published · · By ObjectStack Team

Vibe Coding Technical Debt: Why AI-Built Apps Become Hard to Change

A team's AI-built expense system ran fine for months until tax rules changed and no one dared touch its 12,000 lines of unread code. Why generating definitions beats generating code.

Vibe Coding Technical Debt: Why AI-Built Apps Become Hard to Change
  • Vibe Coding
  • Technical Debt
  • Code Generation
  • Metadata
  • AI Governance
  • Perspective

The short version: The bottleneck with AI-generated code is not writing speed. It is that six months later no one can read it and there is no author to ask. Have AI generate metadata instead of code, and the debt is less likely to compound.

The story starts in month seven.

Mingxiang, the name we will give this retail chain, runs a few hundred stores. Last year, its finance team, about fifty people, was tired of waiting for IT’s queue and used AI to “vibe-code” an expense system. Forms, approvals, ledger integration: it was running in two days, and everyone cheered. For most of a year after, it worked just fine.

In month seven, the tax rules changed: the deduction rate for a certain class of invoice was adjusted, and the expense logic had to change with it. A small thing.

Then the team discovered that no one dared touch it. Behind that “one sentence” were 12,000-plus lines of code the AI had generated, not one of which anyone had read in full. Which files held the deduction logic? Would changing it ripple into approvals and end-of-month reconciliation? No one could say. More awkward still, even the AI that wrote the code could not help: it was a stateless generation session, and the judgments, trade-offs, and reasons for writing it that way left no durable record. All the team could do was feed the 12,000 lines into a new session and ask it to infer the system again.

This small thing ended up taking three weeks. The real cost was more than three weeks: in the pre-launch review, someone caught that the AI had altered a piece of reconciliation logic unrelated to deductions, nearly letting a batch of expenses post wrong amounts into the general ledger. This time it was caught. But it proved one thing: this system no longer has anyone who can guarantee that changing A will not move B.

This is what AI-generated technical debt looks like when it lands on an ordinary team.

A Special Kind of Debt: No Author to Ask

Technical debt isn’t a new term. But AI-generated code owes a kind of debt that didn’t exist before.

Code written by people also rots and goes undocumented. But at least the author may still be around. You can pull in the person who wrote it and ask, “Why was this handled this way?” However bad the code, there is usually a human brain behind it that can explain it.

AI-generated code removes even that last backstop. Its “author” is a stateless model session that disappears the moment generation ends. It did not leave the reasoning behind because there was no durable process to leave. So what you are holding is an implementation that runs, but no one can explain and no one is left to ask. Mingxiang’s 12,000 lines are exactly this debt. It is not just code-quality debt; it is comprehension debt.

The industry pattern is a macro version of the same mechanism: AI increases generation speed, while review, understanding, and business accountability do not scale at the same rate. The speed is real, and the debt is real. It just does not settle on delivery day; it comes knocking in month seven.

But Isn’t Code Generation Getting Better Fast?

Take the rebuttal seriously, or the argument is not fair: models keep getting stronger, the code they can read keeps getting longer, and there are AI code-review and “self-healing” pipelines too. Won’t stronger tools repay these debts automatically?

The expectation has its logic, but it bets wrong on where the bottleneck is.

Progress in code generation solves “writing faster.” But Mingxiang’s problem was never writing too slowly. Quite the opposite: it wrote too fast, producing a system in two days. The bottleneck is on the “comprehend it, be accountable for it” side, and that does not improve linearly with generation capability. Stronger models can generate more code faster, which can also mean accumulating unread implementation faster. AI review can help with a scan, but the judgment of “what this business logic should be” lives in the business context, not in the code.

In other words, pinning your hopes on “the model getting a bit stronger” does not address the review bottleneck.

Compound Interest: Each Change Makes It a Little Less Comprehensible

Why month seven, not month one? Because this debt compounds.

The first change: the AI, without having comprehended the old code, “adds a block alongside it.” The second change: it faces “old code plus last time’s uncomprehended new code,” so it adds another block. Each layer stacks on top of an uncomprehended layer below, and comprehension cost rises faster than the team expects. The risk curve is calm early and then steepens in the middle, calm enough to make you think you are fine until one ordinary change exposes it.

How the difficulty of changing a system shifts over time: generated code rises exponentially, while generated metadata stays flat

There is a simple self-check that lets you see which segment of the curve you are on. Three or more of the five below, and you are already accruing AI technical debt:

  1. There is a block of code in the system no one on the team can explain: “why is it written this way?”
  2. To change one thing, your first instinct is “will this break something else?” rather than just changing it.
  3. Security/alert counts climb every month, and most come from newly added code.
  4. Every time you need a change, you have to feed the whole block back to the AI to “re-read it.”
  5. PRs keep getting bigger — too big for anyone to truly review line by line, so all you can check is “did the tests pass?”

Who Ends Up Paying This Bill

The compound curve eventually becomes a concrete bill, split four ways:

  • The business pays in speed: Mingxiang’s “small change” took three weeks. When every change risks breaking something else, iteration velocity visibly collapses — you thought AI made you faster, and half a year later you’re slower.
  • Risk pays in incidents: that near-miss posting into the general ledger got caught, but the next one might not. Sooner or later, one change will not get caught.
  • The team pays in people: no one wants to maintain a lump of a system they can’t read and that shudders when touched, long-term. The moment the person who understands it best leaves, the system becomes a total black box — and AI code never had a “person who understands it best” to begin with.
  • The company pays in rewrites: when comprehension debt piles up to a certain point, “changing” is already more expensive and riskier than “rewriting,” so the project gets torn down and started over. The bit of time saved on version one is repaid with interest.

None of these four show up in the delight of “a whole system stood up in two days.” They are all booked to the ledger after month seven.

First, to Be Clear: Switching to Metadata Isn’t Free

Pause here, or this becomes another miracle-cure pitch.

Having AI generate definitions instead of code has trade-offs. Declarative metadata covers the 90% that recurs over and over in enterprise apps: objects, fields, relationships, views, permissions, processes, approvals. The controllability it buys comes at the cost of giving up some of the “write it however you want” freedom. If what you are building is a brand-new real-time collaboration engine or a unique graphics algorithm, that genuinely needs real code; metadata cannot help you, and forcing it makes things worse. ObjectStack solves the business systems rebuilt over and over in enterprises, not the next Figma.

And one more layer of honesty: choosing a metadata runtime means entrusting part of the implementation to that runtime. You are betting on its quality and long-term availability. That is a real dependency. Compared with betting on “12,000 lines of code no one reads,” though, betting on a runtime that is repeatedly audited, versioned, and migratable is a better trade.

Change What Gets Generated: Not the Implementation, the Definition

Back to Mingxiang’s expense system. Same requirement, but suppose the AI had originally generated not 12,000 lines of implementation, but a definition like this:

export const ExpenseReport = ObjectSchema.create({
  name: 'fin_expense_report',
  label: 'Expense Report',
  fields: {
    amount: Field.currency({ label: 'Amount', min: 0 }),
    invoice_type: Field.select({ label: 'Invoice Type', options: ['General', 'Special VAT'] }),
    deductible_rate: Field.percent({ label: 'Deductible Rate' }),
  },
});

These few dozen lines are not “an interface you still have to go implement.” They are the system. The runtime (ObjectOS) reads them and derives the data tables, APIs, UI, and governed tools an agent can call. Permissions and approval flows are also declarations attached to it, not hand-written judgments scattered elsewhere.

So what does that month-seven tax-rule change look like now? No longer an archaeology dig, but a single comprehensible change:

- deductible_rate: Field.percent({ label: 'Deductible Rate' }),
+ deductible_rate: Field.percent({ label: 'Deductible Rate', defaultValue: 70 }),
+ // Special VAT invoices now deduct 70% under the new rule; the rule sinks into the definition, approvals and reconciliation follow automatically

This is a diff you can review in five minutes and roll back with one command, not a 400-line PR no one dares merge. Nor will there be a “casually broke reconciliation” incident, because the reconciliation logic is not in this definition; it cannot touch it at all. The difference is not which is more “advanced,” but who, six months later, can still read it, change it, and trace an error.

Why the Compounding Mechanism Gets Cut Off

After switching to definitions, why does that curve flatten? Three points point back to Mingxiang’s story:

  1. No “unread implementation.” The implementation belongs to the one repeatedly-audited runtime shared by all apps, not one copy generated per app. Your app holds only definitions, and definitions are written to be read by people.
  2. No “no one to ask.” The definition is declarative — it wears its intent on its face: what deductible_rate is, who owns it, which processes are affected by changing it, all at a glance, with no need to ask that long-vanished generation session.
  3. The attack surface narrows. Many generated-code risks come from hand-written authorization, query construction, and input handling. Once those are carried by the unified runtime, the categories of vulnerability the app layer can introduce shrink.

Closing

AI-generated technical debt is not inevitable because AI writing code is wrong. It appears when teams take a tool for rapidly generating prototypes and use it directly to build long-term systems. A prototype can be a pile of code no one reads. A system you will run for three years should not be.

Next time you want to use AI to build a business system you truly intend to run long term, do not first ask “how fast does it generate?” Ask the question Mingxiang was forced to face only in month seven: half a year from now, when a rule changes, will I still dare to change it, and can I trace it? Having AI generate definitions instead of implementation code is the path that makes that answer more likely to be “yes.”

npm i -g @objectstack/cli && os start

Define an object, change a field once, and see how clean that change is in the git diff. That is debt that does not have to compound.