The Problem
You vibe-coded a thing. It worked. You vibed more. It mostly worked. Now, three weeks in, the codebase is hard to reason about, the AI tools are giving worse output every session, and you're not sure if it's the AI or the code that's the problem.
It's the code. You have vibe debt: code that grew faster than its structure could support, where the architecture is whatever the AI happened to ship in each session.
Engineers have long had a word for shortcuts that make every future change more expensive - it's called technical debt. Vibe debt is a different problem: regular tech debt accumulates at human pace; vibe debt accumulates at AI pace. The fixes are different too.
The Symptoms (How You Know)
You're in vibe debt if you can check several of these:
- Architecture drift: three different patterns for the same job — e.g., three ways the codebase handles errors, three ways it loads data, three ways it composes components.
- Naming chaos: the AI invented a new noun for a concept that already had a name. Now you have
User,Account,Member, andProfile, all referring to roughly the same thing. - Unowned modules: you can't say what a file is for. It accumulated over four sessions; each session added one thing.
- AI gives worse output now: early sessions felt magical; recent sessions feel like the AI is fighting the codebase. (It is.)
- Re-asking the same questions: you ask the AI the same architectural question across sessions and get different answers.
- Edit anxiety: a small change requires reading three files to be safe.
You don't need all of these — two or three is enough to act.
The standing check
The three moves below are the recovery. The habit that stops you needing them is much smaller: at the end of each session, ask what new patterns turned up that aren't written down anywhere. Vibe debt accumulates at AI pace and gets cleaned up at human pace, so the only version of this that works happens during the sessions, not after.
Make it automatic rather than remembered: "Before we stop — did you introduce any pattern today that isn't already in my conventions file? List them and I'll say keep or kill." Put that in the instructions file your agent reads every session, or wrap it in a skill if yours installs them. What stays yours is picking the zone and picking the move: a measurement can tell you how bad an area is, never whether you can afford a week inside it.
The Three Recovery Moves
You have three moves. Pick by zone, not by codebase — different parts of the same project want different moves.
Move 1: Vibe-Refactor
Have the AI do a bounded refactor: tests-first, scope-locked, with explicit "do not touch X" guardrails.
When to use: the zone has clear behavior you can express as tests, and the chaos is mostly stylistic (naming, error handling, where state lives) rather than fundamental.
The pattern:
# Step 1: Pin the contract
"Read @src/orders/. Don't change anything. Write tests that capture
the current behavior of `placeOrder`, `cancelOrder`, and the order
state machine. Use the existing test conventions from @src/users/users.test.ts.
Run them. Confirm they pass."
# Step 2: Bound the scope
"With the tests passing, refactor @src/orders/ to:
- use one error-handling pattern (the throw + catch in handler one)
- rename `OrderRecord` → `Order` everywhere
- move all DB calls into `db.ts`
Do not touch any file outside @src/orders/.
Do not change behavior — tests must continue to pass.
Stop and ask if you find a third pattern I haven't named."
What can go wrong: the AI quietly expands scope. Tests that pass for the wrong reason hide the breakage. Mitigations: explicit "do not touch" list, run the test suite after each file edit, eyeball the diff before merging.
Cost: low. Hours, not days. AI does the typing.
Move 2: Step Out
You do the refactor by hand. AI helps with mechanical bits - renames, and scripted rewrites that change one pattern everywhere at once, known as codemods - but doesn't drive.
When to use: the zone is the worst zone — multiple intersecting bad decisions, and you can't write tests for current behavior because current behavior is the bug. Also: when the AI keeps producing plausible-looking refactors that subtly break things and you can't tell why.
The pattern: you sit down with the file open, read it end to end, write down what you think it's doing, then rewrite it in your editor. AI is on call for "rename this symbol everywhere" and "find all callers of X" but not driving design.
What can go wrong: you discover the bug-as-feature. Some piece of weird behavior is depended on by another piece of weird behavior. Mitigation: do this work behind a switch you can flip in production without redeploying - known as a feature flag - ship the rewrite alongside the original, and switch over only after a quiet period.
Cost: high. Days, not hours. But it's the only way to get out of zones where the AI can't see the structure.
Move 3: Greenfield
Throw the affected module away. Rewrite it from a clean spec.
When to use: the module's behavior is small enough to re-derive (an isolated feature, not a sprawling one), AND the existing implementation is bad enough that fixing it would cost more than rewriting it. The intersection is smaller than people think — greenfield is rarely the right call.
The pattern: write a one-page spec of what the module should do. Vibe a fresh implementation against the spec, in a separate file or directory. Migrate callers one at a time. Delete the old module last.
What can go wrong: the original module had quietly accumulated edge-case handling that nobody documented. The greenfield rewrite drops those silently and re-introduces bugs that were fixed three months ago. Mitigation: read the old code's git history before rewriting; specifically, read every commit that says "fix" or "edge case." Capture them in your spec.
Cost: medium-to-high. Days for small modules; weeks for sprawling ones (which is when you should not have picked this move).
The Decision Matrix
| Behavior is testable? | Bad enough to scrap? | Move | |
|---|---|---|---|
| Stylistic chaos, behavior fine | Yes | No | Vibe-Refactor |
| Multiple intersecting bad decisions | Hard / behavior is the bug | Maybe | Step Out |
| Small isolated module, deeply broken | Yes (spec exists or can be derived) | Yes | Greenfield |
| Large sprawling module, deeply broken | Hard | Yes — but you can't afford it | Step Out, slice by slice |
Mistakes to Avoid
Refactoring Across Zones
Symptom: "while we're at it, let's also clean up the auth module." Now you have a refactor PR that touches everything and can't be reviewed.
Fix: one zone per PR. Vibe debt makes you want to fix everything at once. Resist.
Vibe-Refactoring Without Tests
Symptom: "the AI says the refactor is safe; let's ship." Tests don't exist. The AI is confident anyway.
Fix: tests first, always. The whole point of move 1 is that the test contract anchors the refactor. Without tests, you're just vibing more debt with extra steps.
Greenfielding the Wrong Thing
Symptom: the module is large, sprawling, and central. The greenfield rewrite drags on for weeks; meanwhile the old module keeps shipping changes that the rewrite has to chase.
Fix: if you can't finish the rewrite in one focused week, you picked the wrong move. Step Out, slice by slice. (See Prototype vs. Production for the related "rewrite addiction" failure pattern.)
The Permanent Refactor Branch
Symptom: the refactor branch lives for a month. The main branch keeps moving. Merging gets harder every week.
Fix: if a refactor isn't merging within a few days, your zone was too big. Cut it smaller and ship.
Prevention
The cheapest fix for vibe debt is not accumulating it. Three habits go a long way:
- End-of-session conventions check. Before closing the laptop: "Are there any new patterns the AI introduced this session that aren't in CLAUDE.md / AGENTS.md?" If yes, add them or delete the new pattern.
- One name per concept. If you see a synonym creeping in, kill it on sight. Renames are cheap when there are five callers; impossible when there are fifty.
- Refactor in the same session as the feature. If you added behavior that should belong somewhere else, move it before the session ends. The next session starts cold and won't notice.
The Pace Asymmetry
Vibe debt accumulates at AI pace. Cleanup happens at human pace. The math only works if cleanup happens during the vibe sessions, not after. Build the habit; the alternative is a recovery move every six weeks.
Cross-Vendor Note
This article is tool-agnostic. The zones, the moves, and the decision matrix don't change whether you're vibing in Claude Code, Codex, or Cursor. The only thing that changes is the surface: in Claude Code you'll do the bounded refactor with @-mention scoping; in Codex CLI you'll do it with AGENTS.md guardrails; in Cursor you'll use the agent panel. Different surface, same discipline.
Quick Reference
You're in vibe debt if you can check 2+ of:
- Architecture drift (multiple patterns for the same job)
- Naming chaos (synonyms for the same concept)
- Unowned modules (can't say what a file is for)
- AI output is getting worse
- Same architectural question, different answers across sessions
- Edit anxiety on small changes
Three moves:
- Vibe-Refactor — tests-first, scope-locked, AI drives. Use for stylistic chaos.
- Step Out — you drive, AI assists. Use for the worst zones.
- Greenfield — throw away and rewrite. Use sparingly, for small isolated modules only.
Rule of Thumb:
Pick by zone, not by codebase. Most projects in vibe debt need different moves in different parts. One PR per zone. End-of-session conventions check is cheaper than any recovery move.