Asking an AI assistant to "improve" or "harden" code rarely trims anything. It adds guard clauses, retries, and validators nobody requested, because the model has no shared context for what "done" means and treats visible change as proof of effort.
The Details:
- Each refinement request starts cold. The model does not remember which approaches it already rejected or why the current version feels finished, so it defaults to the safest signal available: producing something noticeably different from what it received.
- Restraint reads as failure to the model. Returning code nearly unchanged looks like it skipped the work, even when trimming two lines is the correct move, so it reaches for try-catch blocks, helper functions, and defensive checks that cost nothing to write and might match unstated intent.
- Open-ended words like "robust" or "production-ready" invite armor on every surface, null checks, type guards, concurrency handling, with no way to rank which threats actually apply to your system. Breadth feels safe to the model, so it accumulates across rounds even though no single round looks unreasonable.
- The fix is stating both a target and a fence before prompting: name the one concrete problem to solve, such as a specific malformed input you actually saw, and name what should stay untouched. A prompt without both halves hands the ambiguity straight back to the model, which then makes architectural choices you never signed off on.
A useful check after any round of changes: can you name the real bug each addition fixes? If not, that addition was manufactured to look like progress, not to solve anything. Discard it, return to the version you trusted, and reissue a narrower, fenced request instead of asking the model to fix its own excess.
Bottom Line: The model can execute skillfully inside a boundary you set. It cannot decide what better means for your codebase, so if you never draw that boundary, the code drifts one reasonable-looking round at a time.