Debug and Refactor the Code You Didn't Write
An intermediate journey: reproduce a bug, bisect to the breaking change, fix it with a regression test, and refactor AI-generated code without breaking behaviour.
A reproducible bug fix backed by a regression test, a safer refactor of a tangled module, and a codebase you can navigate without asking the assistant what it does.
An app large enough that you have lost track of a file, and at least one bug you cannot currently explain.
Any language. The method is tool-independent; examples assume you have Git and a test runner.
The project
Pick one real bug in your app and one module that has become hard to read. This journey takes you through the full loop — reproduce, locate, fix, pin, restructure — the same way every time, so the next bug is faster and the codebase stops getting worse.
By the end you will have: a failing test that now passes, a named commit that introduced the bug, a refactored module with unchanged behaviour, and a repeatable habit for the next three.
Before you start
- A bug that is currently reproducible, even if only sometimes.
- Git history with more than a handful of commits.
- A test runner wired up, or ten minutes to add one. If you have never written a test by hand, read How to Write Your First Unit Test before you start.
If the current app is already tangled and repeated “fix it” prompts are making it worse, pause and read How to Recover When an AI Build Gets Messy. Preserve the failure and reduce it before making another broad change. If a specific change caused a clear failure, use How to Recover When AI Makes a Mistake in Your Code to contain and roll back safely.
Milestone — Reproduce the bug on purpose
Build: the shortest set of steps that makes the bug happen every time.
You cannot fix what you cannot trigger. “It breaks sometimes” is a feeling; “these four steps break it in 100% of runs” is a bug.
Do it
- Write the failing case as a numbered list, in the app or in an API call.
- Reduce it: delete every step that is not required. Keep deleting until removing anything makes the bug stop.
- Record whether it is deterministic or timing-dependent.
Done when someone else can follow your steps and see the same failure. How to Debug AI-Generated Code covers the reproduction discipline; Debugging AI-Generated API Logic is the version for endpoint bugs.
Milestone — Find the change that introduced it
Build: the exact commit where behaviour broke.
Knowing when a bug appeared usually explains why. Git can binary-search the history for you, so you do not have to read every commit.
Do it
- Find a commit where the feature worked.
- Ask your agent to set up
git bisectwith a script that exits non-zero on the bug. - Let it run and read the first bad commit it reports.
Done when you can name the commit and quote the line that changed. Using Git Bisect to Find the Commit That Broke Your AI Code shows the automation, including how to script a check for a web app.
Milestone — Map the flow before you touch it
Build: a one-page path from the entry point to the failing line.
Reading generated code line by line is slow. Tracing the path the failing request takes is fast, and it is how you find which file is actually responsible.
Do it
- Ask your agent to trace the failing operation and list, in order, every function the request passes through.
- Have it name, for each function, its single responsibility and where the state changes.
- Read only those functions.
Done when you can point at the function whose responsibility is violated. Debugging AI-Generated Code Systematically is the full method, including the log-first technique.
Milestone — Write the failing test first
Build: a test that reproduces the bug and fails for the right reason.
A test is a bug that can never come back. Write it before the fix, and watch it fail — that proves the test actually covers the failure.
Prompt your agent
Here is the reproduction for the bug: <paste the steps>.
Write a test that:
- fails on the current code, for the exact reason the bug
describes (show me the failure output),
- asserts the correct behaviour, not the current behaviour,
- is fast and does not depend on network or wall-clock time.
Do not fix the bug yet.Done when the test fails before the fix and passes after it, with nothing else changed. Unit vs Integration vs End-to-End Tests helps you pick the right level.
Milestone — Fix the cause, not the symptom
Build: the smallest change that makes the test pass and explains the bug.
The temptation is to add a guard where the error surfaced. Resist it: fix the function whose contract was broken.
Do it
- Change one thing at a time; re-run the suite after each.
- Re-run the original reproduction steps, not only the test.
- If the fix is “add a null check,” ask why the value was null.
Done when the test passes, the steps no longer reproduce the bug, and you can explain the cause in one sentence. Then read How to Review AI-Generated Code Like a Senior Engineer and review your own fix the same way.
Milestone — Pin the behaviour with tests, then refactor
Build: a refactored module whose tests were green before and after.
You are about to change structure without changing behaviour. The test suite is the only thing that can tell you whether you succeeded.
Do it
- Pick the module that is hardest to read — usually the one with the longest function.
- If its behaviour is not covered, add tests that pass before you touch it.
- Ask the assistant to extract functions, flatten nesting, and rename for intent, in small steps.
- Run the suite after every step.
Done when the suite is green, the file is shorter and readable, and no behaviour changed. Refactoring AI-Generated Code Without Breaking It and What Is Refactoring? are the reference.
Milestone — Remove what the assistant left behind
Build: dead code, duplicated blocks, and unused dependencies deleted.
Generated code accumulates leftovers: functions nobody calls, the same validation copied three times, packages installed for an approach that was abandoned.
Do it
- Ask your agent to list functions, exports, and dependencies with no references, and verify each claim before deleting.
- Find duplicated blocks and extract them once.
- Remove unused packages and re-run the build.
Done when the deletion commit changes no behaviour and removes real lines. How to Remove AI Dead Code and Avoiding AI Code Duplication cover the safe order to do it in.
Milestone — Stop the next one at the lint gate
Build: linting and tests running on every commit, locally and in CI.
Individual fixes do not compound; a gate does. This milestone makes the improvements you just made the new floor.
Do it
- Add a linter and formatter for your language, with the rules committed to the repo.
- Wire lint and tests into a pre-commit hook and into CI.
- Fix the existing violations in one commit, so the gate starts green.
Done when a commit with a lint error is rejected automatically. See Linting AI-Generated Code and What Is Linting?.
Ship checklist
- The bug has a written, minimal reproduction.
- The commit that introduced it is named.
- A regression test fails before the fix and passes after.
- The fix addresses the cause, not the failing line’s symptom.
- The refactor shipped with tests green before and after.
- Dead code and duplicated blocks are deleted, not commented out.
- Lint and tests block bad commits in CI.
Where this journey goes wrong
- Fixing without reproducing. You patch a symptom and the cause resurfaces elsewhere.
- Letting the assistant “clean up” without tests. Unverified refactors are how working features disappear.
- Deleting on a hunch. Verify every unused symbol with a search before removing it.
- Refactoring and fixing in one commit. If tests fail, you cannot tell which change caused it.
- No lint gate. The same class of defect returns in the next generated file.
FAQ
Shouldn’t I just ask the assistant to fix the bug?
Eventually — but after you have reproduced it and found the introducing commit. A vague symptom in a chat gets a plausible edit that may hide the cause. Reproduce first, then delegate the fix with the reproduction as context.
Is refactoring AI-generated code worth the risk?
Only with tests in place. Refactoring means changing structure without changing behaviour, and without a green suite you cannot tell the two apart. Pin the current behaviour first; then the refactor becomes a boring, safe edit.
Related guides
- How to Recover When an AI Build Gets Messy — how to stop the prompt loop and get evidence first.
- How to Recover When AI Makes a Mistake in Your Code — the incident playbook for a specific bad change.
- How to Learn Software Engineering While Building With AI — turn the bug into the next bounded lesson.
- What Is Technical Debt? — the frame for deciding what to fix now.
- What Is a Code Smell? — the signals worth refactoring for.
- Reducing AI-Generated Complexity — the specific patterns generated code overuses.
- Journey: Add a Database Without Regret — when the bug lives in your data layer.
- Journey: Make Slow Work Reliable — when the bug is a race, not a line.