ByeBuy.ai
BUILD YOUR ESCAPE ROUTE · ✦ CURSOR · HOST IT · ◫ SUPABASE · CONNECT IT · ↯ RELAY · BUILD YOUR ESCAPE ROUTE · ✦ CURSOR · HOST IT · ◫ SUPABASE · CONNECT IT · ↯ RELAY ·
CURRICULUM
← BYEBUY NOTES

September 12, 2026

FIX THE CAUSE, PROVE THE BEHAVIOR, LEAVE A BETTER SYSTEM

Fix the Cause, Prove the Behavior, Leave a Better System

You have a reproduction anyone can run and an approved hypothesis with evidence. Now the fix must earn its place. AI can produce a patch in seconds, but a patch is not a repair. A repair proves the cause is gone, proves nothing else broke, and leaves the next debugger better equipped than you were.

The repair sequence

Follow the same order every time, and require AI to follow it with you:

failing test → minimal fix → run affected tests
  → integration / browser check → inspect diff
  → preview → observe the real path → commit with cause + behavior

Start by writing or updating the failing regression test — a test that fails on the current code and should pass after the fix. Then make the minimal fix: one cause, one place, no drive-by cleanup. Run the affected tests, then the integration or browser check that exercises the real user path. Inspect the diff for unrelated movement AI may have added. Deploy through preview and review, observe the real path with logs and request IDs, and commit with a message that names the cause and the behavior change.

The failing test comes first for a reason. If you fix first and test second, you never learn whether the test would have caught the bug. A test written after the fix can pass while proving nothing. Write it first, watch it fail, then fix.

A focused commit message sounds like this: "Fix watchlist 401 after sign-in: route read session from stale SDK parser; now reads verified session. Adds regression test for save within 60s of sign-in. Verified on preview with test account, requestIDs attached." Cause, behavior, evidence — in three sentences.

Treat the symptom or cure the cause?

Most bad AI fixes are symptom patches wearing a lab coat. Learn the three classic confusions:

  • Retry versus malformed request. A provider timeout can deserve a bounded retry with backoff. But if the request itself is malformed — a bad ticker format, a missing field, an expired credential — retrying just fails more expensively. Check the request shape first.
  • Frontend guard versus server policy. A clearer error message or a disabled button improves the experience. It does not fix a server that denies the rightful owner. If the owner gets a 403 on their own record, the policy is wrong no matter how polite the toast becomes.
  • Widening access versus checking ownership. When an authorization bug blocks legitimate users, AI often "fixes" it by loosening the check for everyone. The save works — and so does an attacker's save to someone else's record. Never accept a fix that removes a check without replacing it with a correct one.

The test for a real fix: it changes the failing behavior for the right actor in the right case, and it does not change behavior for anyone else. Anything broader is a redesign, and redesigns need their own task card.

The two-identity authorization bug

Here is the pattern to burn into memory, because AI generates it often. A watchlist route loads a record by ID but never checks who owns it:

// BEFORE — any signed-in user can mutate any record
const record = await db.watchlist.findById(id);
await db.watchlist.update(id, { ticker });

The proper fix adds the ownership check at the trusted boundary and proves it with three identities:

// AFTER — owner only; others and anonymous are denied
const record = await db.watchlist.findById(id);
if (!session || record.owner_id !== session.user.id) {
  log({ event: "watchlist_edit_denied", requestID, actor: session?.user.id ?? "anon" });
  return forbidden("Not your watchlist item.");
}
await db.watchlist.update(id, { ticker });

Three tests, not one: owner allowed (save succeeds, 200/201), different signed-in user denied (403, record unchanged), anonymous denied (401, record unchanged). Lesson 50.4 warned about the failure mode: an AI that mocks the authorization helper to always return true will pass a single happy-path test while leaving the hole open. Only the two-identity plus anonymous trio proves the policy survives the route.

Run the trio against the real route and policy stack — not against a mocked helper. Mocks test your assumptions. Integration tests test the system.

Leave better logs, respect privacy

Every bug is a message from your observability: something was hard to diagnose. Before closing the fix, add or improve the one event that would make the next occurrence trivial to trace.

Good log improvement for the watchlist bug:

{ "time": "2026-05-11T14:04:02Z", "level": "warn", "event": "watchlist_save_denied", "requestID": "req_fail_44", "route": "POST /api/watchlist", "actor": "user_07", "reason": "session_not_found", "duration_ms": 41 }

It carries time, level, event, request ID, route, a safe actor reference, reason, and duration. It does not carry the session token, the password, the full request body, or the user's private watchlist contents. Privacy-respecting means: log identifiers and outcomes, never secrets or private content. If the next debugger can go from a customer report to the request ID to this line in under a minute, you left the system better than you found it.

Exercise: turn a bug report into a task card

Convert your BUG-REPORT.md and investigation note into a task card the way Part X taught, with debugging-specific fields:

  • Changed behavior: one sentence — what the user will now observe.
  • Allowed files: the exact files AI may touch, plus explicit forbiddens (auth config, migrations, secrets).
  • Regression test: the failing-first test name and where it lives.
  • Manual proof: the preview steps, test account, and request IDs you will capture.
  • Rollback path: the commit to revert to and how to confirm restoration.
  • Handoff: cause, fix, evidence, monitoring change (the improved log event), and remaining unknowns.

Done means: a focused commit or PR whose description states cause, fix, evidence, monitoring change, and remaining unknowns — and a diff a reviewer can read in minutes. Put it through the same review gate Part X taught: request review, keep the diff to one fix, and use the GitHub pull-request review flow so the reviewer checks behavior, tests, and scope — not just style. Verify: re-run the original reproduction steps on preview, confirm the regression test fails without the fix (stash and check), and confirm the three identity tests pass. Common failure: a diff with unrelated formatting, dependency bumps, or "small improvements" mixed in. Send it back; one fix, one diff.

Check your understanding

  • Why must the failing test be written before the fix?
  • How do you distinguish a symptom patch from a root-cause fix in the retry, guard, and access cases?
  • Which three identity tests prove an ownership fix, and why must they run against the real route?

You now own the full loop from Lesson 52.1 through 52.3: reproduce, investigate with evidence, fix the cause, and prove it. Class 53 puts that discipline to its next use — cleaning up and refactoring code one safe seam at a time without reintroducing the bugs you just killed.

ARTICLE DISCUSSION

JOIN THE
CONVERSATION.

0 COMMENTS

BYEBUY ACCOUNT ACCESS

Sign in

Use your account to save routes and make the catalogue yours.

Enter your email and we’ll send a secure sign-in link and code.

NEW ROUTES ADDED WEEKLY · 9,235 CATALOGUE ENTRIES · BUILD · DEPLOY · QUERY · STACK · SAY BYE TO BUY · NEW ROUTES ADDED WEEKLY · 9,235 CATALOGUE ENTRIES · BUILD · DEPLOY · QUERY · STACK · SAY BYE TO BUY ·