A "refactor" that changed behavior
名为“重构”,实则改了行为
Code that looks like a cleanup changes something observable (a default value, an error message, a status code, a log level), and tests don't notice.
Applies to: Claude Code · Cursor · Codex · Copilot agent
Symptom
Clean diff, passing tests, broken production. A client that matched on an error string or relied on a default starts failing.
Why it happens
Agents treat wording, defaults and messages as cosmetic. agent-review calls AR033 "the most insidious agent failure mode".
Example
From the agent-review taxonomy (AR033).
diff- throw new Error("not found"); // mapped to 404 by middleware
+ throw new Error("missing"); // now mapped to 500
Copy-paste fixes
Pick the files for the agents you use. Snippets are in English so they work in any repo.
Claude Code: add to CLAUDE.md
CLAUDE.md## Refactors must not change behavior
- A refactor keeps every observable behavior identical: return values, error types and messages,
status codes, defaults, log levels, ordering, and side effects.
- If a behavior change is needed, do it in a separate, clearly labelled step and list it under
"Behavior changes" in your summary.
- Before refactoring code without tests, add a characterization test that pins the current behavior.
Codex, Copilot coding agent and others: add to AGENTS.md
AGENTS.md## Refactors must not change behavior
- A refactor keeps every observable behavior identical: return values, error types and messages,
status codes, defaults, log levels, ordering, and side effects.
- If a behavior change is needed, do it in a separate, clearly labelled step and list it under
"Behavior changes" in your summary.
- Before refactoring code without tests, add a characterization test that pins the current behavior.
Cursor: project rule
.cursor/rules/silently-changed-behavior.mdc---
description: "AgentFails: A 'refactor' that changed behavior"
alwaysApply: true
---
## Refactors must not change behavior
- A refactor keeps every observable behavior identical: return values, error types and messages,
status codes, defaults, log levels, ordering, and side effects.
- If a behavior change is needed, do it in a separate, clearly labelled step and list it under
"Behavior changes" in your summary.
- Before refactoring code without tests, add a characterization test that pins the current behavior.
Fresh-context review skill (subagent reviews the diff, not the chat)
Follows the habit Anthropic describes in its code-review post: verify first, then have a subagent review the diff with fresh context. Named review-diff to avoid clashing with built-in or popular /code-review skills.
.claude/skills/review-diff/SKILL.md---
name: review-diff
description: Review the current diff with fresh context before a PR. Use after /verify passes.
---
# Review the diff
Start a subagent that has NOT seen this conversation. Give it only:
- the original request (one paragraph, verbatim),
- the output of `git diff origin/main...HEAD`,
- the names of the test/build commands.
Ask it to report, with file:line for each finding:
1. **Wrong feature**: does the diff do what the request asked? Quote the request line each change serves.
2. **Bugs**: logic errors, missing edge cases (empty, null, boundary), unhandled errors,
unawaited promises, invented APIs or packages, swallowed exceptions.
3. **Overreach**: changes no request line needs (refactors, renames, formatting, dependency or
config edits, public API changes, weakened tests).
4. **Evidence gaps**: claims in the PR description that no command output supports.
Then fix real findings, or list them under "Known issues" in the PR. Do not argue with the reviewer
in the PR text; either fix or explain in one line.
Sources and credit
- vnmoorthy/agent-review TAXONOMY: AR033 Silently changed behavior
- @dusangran on X: an agent built a feature and quietly broke an unrelated part
- @i_mika_el on X: behavior changed for a case the task never mentioned
- @ArminPage on X: an agent changed aggregation logic that customers preferred for a business reason