What's different about it
Debugging your own code starts from memory: you know why each branch exists, so a wrong result narrows quickly. With generated code that memory doesn't exist. Every line is equally plausible and equally unexamined, and the code reads as though someone competent thought about it — which suppresses exactly the suspicion you need.
So the order changes. Instead of reasoning from intent, verify from the outside in: does it exist, is it called correctly, is the data what you assumed, and only then is the logic right.
The order of attack
- Does everything referenced actually exist?Imports, methods, config keys, environment variables. Invented-but-plausible APIs are the most common failure and the cheapest to rule out — types and a run find them in seconds.
- Is the data the shape you assumed?Log the actual input at the boundary before reasoning about the logic. A large share of these bugs are a field that's a string where the code expects a number, or null where it expects absent.
- Are the edges handled?Empty array, missing optional value, failed request, zero, negative, duplicate. The happy path is nearly always right; the edges are where confident guessing shows.
- Is there a second implementation of this?Convention drift means the bug is often that two date helpers exist and the wrong one is being called.
- Only now, read the logic line by lineBy this point the search space is small enough that reading is efficient rather than a slog.
Bug classes specific to this workflow
| Class | Looks like | Found by |
|---|---|---|
| Invented API | A sensible method that doesn't exist | Types, linter, running it |
| Silent catch | try/catch that logs and continues | Searching for catch blocks |
| Off-by-one at boundaries | Correct in the middle, wrong at the ends | Testing with 0, 1, and empty |
| N+1 query | A loop containing an await on I/O | Searching for await inside for |
| Wrong layer fix | A null check where the symptom appeared | Asking where the value came from |
| Timezone and float money | Correct locally, wrong in production | Testing with a non-UTC date and .1 + .2 |
// Looks responsible. The save didn't happen and nobody knows.try { await saveInvoice(invoice);} catch (error) { console.error(error);} // The caller needs to know, and the message needs to say what failed.const result = await saveInvoice(invoice);if (!result.ok) { return { ok: false, reason: `Invoice ${invoice.id} not saved: ${result.reason}` };}Using the model to debug, without letting it guess
A model is genuinely useful here, provided it's given evidence rather than asked to speculate. Asked "why is this undefined" with no data, it will produce a plausible cause and a fix for it — and the fix will be applied at the point of the symptom.
- Paste the full stack trace, not your summary of it
- Paste the actual value that was wrong, and what you expected
- Say what you've already ruled out, so it stops proposing those
- Ask for possible causes ranked by likelihood, before asking for any fix
- Ask "where could this value have come from?" — that question moves the search upstream, where the cause usually is
When to delete instead of repair
Sunk cost applies strangely to generated code: it cost almost nothing to produce, and people still defend it as though they'd written it. Often the right move is to delete the block and regenerate with what you now know.
- Three failed fixes on the same block — the approach is probably wrong, not the details
- You can't explain what a section does after reading it twice
- The fix requires understanding an abstraction that exists for one caller
- It's over-engineered relative to the problem, which is a common default
Regenerating with the constraints the failure taught you — "one caller, no options object, handle the empty case explicitly" — usually produces something smaller and correct in one attempt.
Common mistakes
- Reading the logic before verifying the data and the imports
- Accepting a fix at the point of the symptom
- Trusting a catch block that logs and continues
- Debugging by asking the model to speculate without evidence
- Repairing a block for an hour that would take five minutes to regenerate
Key takeaways
- Verify existence, then data shape, then edges, then logic — in that order
- Invented APIs, silent catches, N+1 loops, and wrong-layer fixes are the recurring classes
- Give the model evidence and ask for ranked causes before any fix
- "Where did this value come from?" is the highest-yield debugging question here
Try it yourself
Search your codebase for catch blocks that only log. Each one is a place an operation can silently fail. Pick the one on the most important path and make it report the failure to its caller.
