Why Every Fix Breaks Something Else: Debug Framework + Root Cause Analysis
Fixing one bug and watching two new ones appear is not bad luck—it's a structural problem. This article explains why AI-built codebases fall into cascading fix loops, and gives you a five-step framework to stop patching symptoms and start solving root causes.

You fix one bug. Two new ones appear.
This is the most frustrating pattern in AI-built MVPs—and it’s not random.
It happens because:
- Business rules are scattered (validation in 5 places)
- Logic is duplicated (same code copy-pasted)
- Dependencies are unclear (change A breaks B, no one knows why)
This article explains why it happens and how to stop it.
For broader context:
Why Fixes Break Things: The 3 Root Causes
1. Scattered Business Rules
Example: Payment validation logic.
In a healthy codebase:
- One place:
rules/payments.ts→ all payment logic here.
In AI-built MVP:
- Five places: checkout page, payment API, webhook handler, admin panel, email template.
- Fix validation in checkout → breaks webhook (different rule).
Result: Fix one bug, create regression.
2. Duplicated Logic
Example: User role checks.
Healthy:
// utils/permissions.ts
export function canEditProject(user, project) {
return user.role === 'admin' || user.id === project.owner_id;
}
AI-built MVP:
// 5 different files, 5 slightly different checks
if (user.role === 'admin' || user.id === project.owner) { ... }
if (user.role === 'admin' || user.userId === project.ownerId) { ... }
if (user.isAdmin || user.id === project.owner_id) { ... }
Result: Fix one check → 4 others still broken.
3. Unclear Dependencies
Example: Order state machine.
Healthy (explicit dependencies):
- Order created → Email sent → Inventory updated → Payment charged.
- Each step documented, tested.
AI-built MVP (implicit dependencies):
- Order created → ???
- Sometimes email sent before payment (bug: email says “paid” but payment fails).
- Sometimes inventory updated before payment (bug: item reserved but payment declined).
Result: Fix email timing → breaks inventory logic.
Real Example: The Checkout Bug Loop
Week 1: User reports: “Checkout button doesn’t work.”
Fix: Add onClick handler. Deploy.
Week 2: User reports: “Checkout charges twice.”
Fix: Add idempotency key. Deploy.
Week 3: User reports: “Order confirmation email not sent.”
Fix: Add email trigger after payment. Deploy.
Week 4: User reports: “Email sent even if payment fails.”
Fix: Move email trigger after payment success check. Deploy.
Week 5: User reports: “Inventory not updated.”
Fix: Add inventory update after payment. Deploy.
Week 6: User reports: “Inventory updated even if order canceled.”
Result: 6 weeks, 6 bugs, each fix creates new regression. Original issue (checkout button) still fragile.
Root cause: No centralized order workflow. Each fix = patch on patch.
5-Step Debugging Framework (Stop the Loop)
Step 1: Isolate the Bug
Don’t: Prompt AI: “Fix checkout bug.”
Do: Reproduce bug manually. Document exact steps.
Example:
- Login as user X.
- Add item Y to cart.
- Click checkout.
- Enter card 4242 4242 4242 4242.
- Click “Pay.”
- Observe: Charged twice.
Output: Reproducible test case.
Step 2: Test First (Before Fixing)
Write test that fails:
test('checkout charges once', async () => {
const order = await createOrder({ userId: 1, itemId: 5 });
const charges = await getCharges(order.id);
expect(charges.length).toBe(1); // Fails (currently 2)
});
Why: Test ensures fix actually works (not just “looks fixed”).
Step 3: Centralize Logic
Before (scattered):
- Checkout page: calls Stripe API.
- Webhook handler: also calls Stripe API (different params).
- Admin refund: also calls Stripe API (third variation).
After (centralized):
// services/payments.ts
export async function chargeCustomer(userId, amount, orderId) {
// Single source of truth for payment logic
const idempotencyKey = `order_${orderId}`;
return stripe.charges.create({ amount, idempotencyKey });
}
Result: Fix once, applies everywhere.
Step 4: Document Dependencies
Create order workflow doc:
Order State Machine:
1. Created → send email ("order received")
2. Payment charged → update inventory
3. Payment success → send email ("order confirmed")
4. Payment failed → send email ("payment issue") + release inventory
5. Order canceled → refund + release inventory
Why: Explicit dependencies prevent “fix A breaks B” surprises.
Step 5: Prevent (Not Just Fix)
Add guard rails:
- Linting: ESLint rule: “No direct Stripe calls outside
services/payments.ts.” - Tests: Every fix = new test (prevent regression).
- Code review: Human checks AI-generated fixes (don’t merge blindly).
Result: Future bugs caught before production.
Cost of Broken Fixes vs Prevention
| Approach | Timeline | Cost | Outcome |
|---|---|---|---|
| Patch-on-patch (no framework) | 6-12 weeks firefighting | €3k-€8k (100-200h debugging) | Velocity drops, bugs multiply |
| 5-step framework (prevention) | 1-2 weeks setup | €1k-€2k (15-30h one-time) | Bugs decrease, velocity stable |
| Rescue later (rebuild parts) | 8-12 weeks | €8k-€15k (rebuild checkout flow) | Clean slate, but lost time |
Insight: Prevention (€1k-€2k upfront) saves €2k-€13k vs patch-on-patch or rescue.
How AI Makes This Worse
AI optimizes for:
- Fast output (generate code quickly).
- Happy path (user does everything right).
AI does NOT optimize for:
- Centralized logic (each prompt = isolated solution).
- Edge cases (“what if payment fails mid-process?”).
- Dependencies (AI doesn’t know your full codebase).
Example: Prompt: “Add email after payment.”
AI generates:
await stripe.charges.create(...);
await sendEmail('order_confirmed', user.email); // Bug: no error handling
Problem: If email fails, user charged but no confirmation. AI doesn’t know to:
- Check payment success first.
- Handle email failure gracefully.
- Log for debugging.
The fix is not to stop using AI—it’s to use AI as an accelerator with human guardrails, not as an autopilot.
When to Centralize Logic
Trigger: When you fix same bug 2+ times.
Signs you need centralization:
- Same validation logic in 3+ files.
- Copy-paste code (with small variations).
- “I fixed this last week, why is it broken again?”
Action: Stop adding features. Spend 1-2 weeks refactoring.
Cost: €1k-€2k (15-30h).
Payoff: Velocity increases (fixes work first try). The prototype-to-product hardening checklist gives you the full priority matrix for this kind of stabilization work.
Conclusion: Fix the System, Not Just the Bug
Every fix breaks something when the system is fragile.
The solution is not “be more careful.” It’s:
- Isolate bugs (reproduce reliably).
- Test first (write failing test).
- Centralize logic (one source of truth).
- Document dependencies (explicit workflows).
- Prevent future bugs (linting, tests, review).
Remember: Patch-on-patch costs €3k-€8k + 6-12 weeks. Prevention costs €1k-€2k + 1-2 weeks (saves €2k-€6k).