Refactoring sounds like safe work: we change the form and leave the behavior. From this definition grows a dangerous corollary - since the behavior does not change, there is seemingly nothing to check. This is exactly where the main trap hides: "behavior preserved" is a claim, and every claim is either measured or stays a hypothesis in a convenient wording.
The naive move is to trust the intention. We meant only to move and rename, so on the outside everything is as it was; the agent is careful, the diff looks mechanical. This confidence grows from the fact that refactoring feels like rearranging rather than changing. But the model does not know that you "meant only to move" - it sees the code and is free to touch more than you were holding in your head.
It breaks at the border between internal structure and the public contract. Until it is written out what exactly counts as observable behavior, there is nothing to tell "we changed only the form" from "we changed the meaning while at it". So the first step is to list the public contracts: signatures, serialized data shapes, errors and exceptions, side effects and performance-sensitive paths. This is the perimeter that refactoring is obliged to leave immovable.
The second step is to make preservation measurable before the rearranging begins. The tool for this is characterization tests: tests that fix the current behavior as is, including its quirks, before the structure is changed. They answer not "how it should be" but "how it is now", and that is precisely why they serve as a reference: if after the refactoring they are still green, the observable behavior on the covered perimeter has not moved.
The change itself is carried out in small compilable steps, not in one big leap. Each step leaves the code built and the tests green, so a regression is localized to the step where it appeared rather than hunted for across the whole diff at once. A large monolithic refactor deprives you of that localization: when everything broke at once, it is unclear which of ten changes is to blame, and the cost of sorting it out exceeds the cost of the rebuild itself.
Separately, one should firmly forbid work done "while at it". Bumping dependency versions, reformatting the whole repository, tidying adjacent files - each of these actions is harmless on its own, but mixed into a refactoring it destroys its main property: the ability to assert that behavior did not change. A useful technique is to require a separate report of intentional behavior changes; in an honest refactoring it is empty, and an empty report is easy to reconcile with the diff and the tests. Such a report is useful as a discipline of thought too: to write "no intentional behavior changes" honestly, the agent is forced to review its own diff for the unnoticed - and often it is right here that the "while at it" surfaces.
The cost of a skipped measurement shows up not at once and is therefore deceptive. Refactoring without characterization often "passes": the code compiles, a quick look finds no problems, the task is closed. A shift in behavior surfaces later - on a rare path, with certain data, at a specific API consumer - and by then it is no longer linked in anyone's head with that rebuild. A deferred defect is more expensive than an immediate one by exactly the cost of its late discovery.
This does not mean any rename requires the full protocol. A local edit inside a single function that does not cross the public contract is checked by the existing tests and by reading the diff. The protocol with characterization pays off where the refactoring touches the contract, spans several modules or prepares the ground for a migration: the wider the perimeter and the more external consumers the code has, the more expensive it is to do without a measurable reference.
You must check the preservation of behavior by the same perimeter you wrote out at the start. A green suite is weak on its own: if the tests do not cover the listed contracts, the wording "behavior preserved" stays a hypothesis, however confidently the green glows. The honest check sounds like this: for each item in the list of contracts there is a test that guards it, and before the refactoring that test already passed on the old code. Then green afterward is truly a proof, not a coincidence. Otherwise the suite merely confirms that the code still does what the tests never checked in the first place.
The typical failures cluster around the skipped measurement. No list of contracts - it is unclear what was being guarded at all. No characterization - "preserved" rests on a word. An allowed "while at it" - and one can no longer tell a rebuild from a quiet change of meaning. A weak suite - the green glows while the contract is uncovered. The sign of all of them is the same: preservation of behavior is asserted, but the test that measures it cannot be shown. Make it measurable in advance, and refactoring stops being a bet.