Why AI coding agents keep breaking things that already worked
AI generated code technical debt has a shape — agents drift toward their own defaults and quietly break code that already shipped. Why it happens, and what actually holds the line.
You ask the agent for one feature. It ships the feature. And something you wrote three weeks ago — something that worked, that had tests, that you'd already forgotten about — is subtly broken. Not with a compile error you'd catch on save, but quietly: a filter dropped, a check inverted, a convention re-invented three lines away from the old one. If you code with Claude Code or Cursor every day, you know this pattern. It isn't bad luck, and it isn't the model being dumb. It's the visible surface of AI generated code technical debt, and it has a specific, diagnosable shape.
This post is the diagnosis, not the sales pitch. The model is remarkably good at the feature you asked for. The problem is everything it didn't ask you about first — and why "regressing code that already worked" is the default outcome, not the exception, once an agent is writing a meaningful share of the repo.
The debt is measured now, and it doesn't behave like the human kind
For a while "AI slows you down eventually" was a vibe. It isn't anymore. One 2026 analysis put numbers on it: roughly a 41% increase in code complexity and ~30% more static-analysis warnings in AI-heavy codebases, alongside the now-familiar arc where "month one feels electric… by month six teams are slower than before adopting AI tooling," with architectural inconsistency compounding as Claude, Cursor, and GPT all touch the same long-lived codebase (Tembo, as of July 2026).
The reason that arc bites harder than ordinary tech debt is that this debt is a different kind of mess. Human debt is usually consistent-but-wrong: a bad pattern repeated, which means it's also repeatable to fix. Agent debt is inconsistent-but-plausible. As one Hacker News commenter put it, "the type of messes created by humans and the type of messes created by genAI are immensely different" (HN, as of July 2026). And it doesn't amortize the way you're used to — "tech debt isn't paid down, it's being added to, and at some point in the future it will need to be collected" (same thread). Every session that re-derives your conventions adds a little more.
Why an agent breaks code it isn't even looking at
The regressions feel spooky because the agent often breaks a file it never opened. There are three mechanical reasons for that.
It drifts toward its own defaults
An agent sees a slice of your codebase per session and fills the gaps with its priors. Each individual choice is reasonable; the sum is a codebase you wouldn't have designed. A developer who wired his whole repo around agent-guardrails described the failure precisely: "the project's shape would drift toward the AI's defaults — reasonable choices in isolation, stacked into something I wouldn't have designed" (dev.to/xiunotes, as of July 2026). When the new default silently disagrees with the old one, the older code is what gives.
It doesn't carry your patterns between sessions
Human authors accumulate a mental model — this is how we scope queries here, this is why that middleware exists. Agents don't. A codebase's consistency, another practitioner noted, is something "llm code has none of… if yes it's by pure chance that won't repeat" (HN, as of July 2026). And even you, reviewing the output, are at a disadvantage the author never had: "you will never know code as well as a reader and you would have as the author for anything larger than a very small project" (same thread). The agent writes as a reader, hands it to you as a reader, and the authorial context that would have flagged the regression is nowhere in the loop.
The regressions cluster in the parts that don't announce themselves
Ask where the breakage lands and the answer is grimly consistent: the boring, dangerous plumbing. Agents "generate RLS policies that accidentally expose data to other tenants, forget to validate webhook signatures on payment events, [and] create permission checks that authenticated users can bypass with modified requests" (MakerKit, as of July 2026). The through-line is that this is "plausible-looking security code that can have subtle flaws" — code that typechecks, reads fine in review, and ships. A forgotten tenant filter that still compiles is the canonical example; if you want the worked version of catching exactly that class of bug at runtime, we wrote it up in the tenant-isolation guard post.
Types catch the loud failures and miss the quiet ones
Here's the good news, and its ceiling. A large share of what agents get wrong is
the kind of thing a type system rejects for free: GitHub reports that 94% of
LLM-generated compilation errors are type-check failures
(github.blog, as of July 2026).
On a strict-typed, contract-first codebase, that entire 94% never survives to a
diff. This is a real reason typed contracts and explicit error types earn their
keep with an agent in the loop — the argument we make in
our DX-is-a-feature philosophy: a contract the agent can
read as the source of truth, and a Result<T, DomainError> it can reason about
without running the code.
But types check the shape of the code, not its intent. A findMany that
dropped its where: { organizationId } typechecks perfectly — it's still a
valid query, just against every tenant at once. The permission check the agent
inverted is well-typed. The webhook signature it forgot to verify was never a
type error to begin with. The compiler catches the loud 94%; the quiet
remainder is precisely the security-and-tenancy class that ships and then leaks.
The fix is structural because the debt is structural
If the debt comes from the agent not carrying your patterns, no amount of asking nicely fixes it — the ask evaporates at the end of the session. The emerging consensus among people who work with agents seriously is to stop treating the repo as a pile of code and start treating it as a harness: "a repository should be treated less like a pile of code that can be executed, and more like an execution environment for agents" (dev.to, as of July 2026). The same discourse is blunt about the price of admission: "if you want to work with AI code systems successfully then you better apply these exact same efforts. Documentation, composition, validation, evaluation, review" (HN, as of July 2026).
The important distinction is between a convention and a wall. A CLAUDE.md
or AGENTS.md that says "don't import infrastructure from the domain layer" is a
convention — the agent can read it, agree with it, and rationalize past it three
tokens later. A lint rule that fails the same import is a wall the agent hits and
cannot argue with. In this repo that boundary is enforced, not suggested:
{
files: ['**/modules/*/domain/**/*.ts'],
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['*/application/*', '*/infrastructure/*', '*/interfaces/*'],
message:
'domain/ may not import from application, infrastructure, or interfaces. See ADR-001.',
},
{
group: ['express', 'prisma', '@prisma/client', 'better-auth'],
message: 'domain/ must remain framework-agnostic. See ADR-001.',
},
],
},
],
},
}The agent doesn't need to know the layering rule or remember it between sessions. When it drafts a domain entity that reaches for Prisma — a reasonable default in isolation — the lint fails with a message that tells it exactly what it did and where the rule is written down. The pattern that was going to erode survives the session because it isn't stored in the agent's context; it's stored in the toolchain the agent runs against.
That's the whole shift. You can't keep an agent from drifting toward its defaults, and you can't make it carry your architecture between sessions. What you can do is make the boundaries something the compiler, the linter, and CI enforce — so a context-limited agent (or a tired human) can't cross them by accident, and the code that worked yesterday still works after today's feature lands. The set of walls that does this — DDD boundaries enforced in lint, a typed client-server contract checked in CI, an E2E gate, a tenant-isolation guard that fails loud — is what we've collected for exactly this failure mode on the page for AI-native developers. The debt is structural. The only durable fix is too.