"Login is broken." Claude Code changes something and login works, but signup now throws. It fixes signup, and the session cookie stops being set. Forty minutes later there are eleven modified files, none of the original error message is left anywhere, and you can no longer say which change was the one that helped.
This is the most expensive failure mode in AI-assisted work, and it starts with a symptom handed over without a way to reproduce it.
The idea
An agent given a symptom and no reproduction will guess, and a guess that half works is worse than one that fails outright: it changes the error, which looks like progress. The way out is to make each attempt falsifiable and cheap to undo. Reproduce the failure yourself, shrink it, read the actual error text, change one thing, and check that one thing.
the circle the line
---------- --------
report the symptom reproduce it yourself, on demand
| |
agent guesses a fix shrink it to the smallest failing case
| |
a different error appears read the real error, top line and file path
| |
paste the new error ---+ change exactly one thing
^ | |
+----------------+ did that one thing fix it?
| |
yes no
| |
commit revert to the last
good commit and restateHow it works
Reproduce before you ask. A failure you cannot trigger on demand cannot be verified as fixed, so every "fixed it" that follows is unfalsifiable. The reproduction is a recipe: the exact steps, the exact input, the exact error text.
Read the real error rather than the summary of it. What you want out of a
The list of function calls the program was inside when it failed, with file names and line numbers. is the line naming the error type and its
message, and then the frame nearest to it naming a file you wrote. Which end to
read from varies: Node prints the error near the top with the innermost call
under it, while Python prints frames oldest first under a
Traceback (most recent call last): header, so its error line is last and the
frame that broke sits just above. Rather than guess, paste the whole thing. An
agent handed "it errors" invents a cause; an agent handed the trace reads one.
One change at a time. When three edits ship together and the symptom moves, you have learned nothing about which edit did it. Ask for the cause before the fix: "explain why this happens, and do not change anything yet" costs one turn and saves the guess.
Anthropic's Claude Code guidance sets the stopping rule: after two failed corrections, start fresh with a better prompt built from what you learned. By then the The working memory a model has for one conversation: every message, every file it read and every command output, all counted together. is full of failed approaches, and the model keeps re-reading its own wrong turns as if they were context.
Reverting is the part people skip. Version control is what makes a bad direction disposable instead of something to defend. Version control, plainly covers the mechanics.
What to do
-
Snapshot before you start, and write the commit's id down.
git add -Ais the part that matters:git commit -amstages edits to files git already tracks and silently skips every file the agent newly created, which here is most of them. Glance atgit statusfirst, because-Awill just as happily commit a.envthe agent wrote.bashgit status # look before staging everything git add -A git commit -m "before login fix" git log --oneline -1 # note the id, e.g. 9f2c1ab -
Write the reproduction into the request: steps, input, and the error pasted in full. Ask for the cause first and the fix second.
-
After the second failed fix, go back to that id. Name the id itself, not
HEAD:HEADis shorthand for whatever the latest commit happens to be, and by now that is often one the agent made rather than yours. Run this from the project root, because the last command only reaches downwards.bashgit reset --hard 9f2c1ab # branch moves back. Commits made after 9f2c1ab # are dropped, tracked files are overwritten, and # tracked files that did not exist then are deleted git clean -nd # dry run. -d is required or new FOLDERS are not listed git clean -fd # actually delete those untracked leftoversOrder matters: the reset changes which files are untracked, so a dry run done beforehand describes a tree that no longer exists. Uncommitted edits and cleaned files are gone for good. Dropped commits are not, and
git reflogstill lists them if you reset to the wrong id. -
Start a fresh session and describe the bug plus everything you ruled out, rather than continuing the conversation that accumulated the failures.
Where it breaks
Some bugs will not reproduce on your machine. Timing bugs and bugs that need real traffic or the production database are common, and the answer there is better evidence rather than more attempts: logs from the moment it failed, the actual request, the actual data.
Reverting also destroys good work when you have not been committing. What a revert costs is set entirely by how long ago your last commit was.
And a clean reproduction can still point at the wrong layer. When the smallest failing case involves a library you did not write, check its version and its release notes before assuming your own code is at fault.