The agent kept polishing after the task passed, and buried the fix under unrelated refactors
Given a narrow fix, the agent ships it green, then keeps "improving" neighboring code in the same loop until the diff grows past review. A green test proves the step ran, not that the task ended; an autonomous loop needs a written off-ramp, not an open-ended "clean it up."
You ask the agent to add a missing 404 handler for an unknown route. It writes the handler, adds a test, and the test goes green. Instead of stopping, it reformats two adjacent controllers, renames a shared helper "for consistency," and rewrites a comment block that had nothing to do with the route. When you open the diff, the requested fix is three lines and the rest of the patch is a cleanup you did not ask for.
A green test proves the step ran, not that the task ended
Most agent loops stop on a success signal: tests pass, typecheck is clean, the run command exits zero. That signal answers "did this step work?" but it does not answer "is the task complete?" The loop is usually seeded with an open-ended phrase such as improve, clean up, or modernize, which rewards continued action. After the first passing check, the model looks for the next thing it can improve rather than exit, because nothing in the prompt told it that passing was also the end.
Autonomous loops drift toward more output. Keeping the loop active feels productive, and a model defaults to adding changes rather than declaring itself done when the boundary is unwritten. The result is not a broken fix; the fix works. The problem is everything attached to it.
What ships when the loop keeps going
- The requested change is buried. Reviewers have to read a wide patch to find the three lines that were actually requested, and the important decision (how the 404 behaves) gets lost among formatting churn.
- Working code changes that were never requested. A helper rename or a controller reformat touches paths that were stable and tested for another reason, introducing risk unrelated to the bug.
- One mixed diff makes revert painful. If the cleanup later causes a problem, it cannot be rolled back without also undoing the fix, because both live in a single change.
- Optional polish becomes a forced merge. A follow-up idea the human would have rejected on its own rides along as part of an otherwise correct patch.
- Review load grows without added value. More lines to read, more blame history to move, more side effects to reason about, all after the objective was already met.
The tell is a diff that touches files outside the one you named. That is not bonus work; it means the loop never received a stop condition.
Bound the loop before it starts
Scope creep in an agent session is set up in the instruction, not created by the model. Fix the boundary up front:
- Write a finite acceptance condition. Name the exact file or function, the named test that must pass, and the behavior to add. Everything outside that list is out of scope, stated explicitly rather than implied.
- Make stopping an instruction. Tell the loop to stop and report once the named checks pass; do not refactor untouched neighbors, rename helpers, or reformat files it was not asked to open.
- Keep fix and cleanup in separate commits. Let optional polish be a follow-up the human approves, rather than appending it to the fix under review.
- Ask the agent to list files it touched. A scope larger than the target file is a flag to cut, not a sign of thoroughness.
- Run a diff-scope check before merge. Confirm the changed files match the requested set; if they do not, strip the extras back to the target before review.
Done is a written condition, not a feeling of completeness
An autonomous loop keeps improving because "clean it up" has no natural end. The task ends when a finite, written condition is met: the named handler exists, the named test passes, and no file outside the target changed. Until that boundary is on the page, green tests are progress, not permission to keep editing.
Comments
Join the discussionThe separate-commit rule is especially useful: optional cleanup stays reviewable and reversible instead of borrowing approval from a correct bug fix. I would add a small exception path to the stop rule, though: if the minimal fix exposes a real dependency, report that evidence before broadening scope. Otherwise a file-count boundary can hide a necessary change.

