你让 Claude Code 改 billing.ts 里一个函数名。Diff 回来 12 个文件 200 行——4 个不相关模块”一致性 pass”、你没要的格式改动、8 个文件的 import 顺序”小改进”。原任务 5 分钟,review 现在 30 分钟。
没显式边界时,Claude 沿着它认为”相关”的代码路径走。Import 引向 caller、caller 引向其他模块、模块各有不一致——一个文件的活儿就变重构了。修法:钉死可编辑文件清单、相邻问题导向 FOLLOWUPS.md 不顺手改、review 时拒 scope creep。
常见原因
按命中率从高到低:
1. Prompt 没指文件边界
「把 getUser 改名 findUser」没说在哪儿。Claude 全局改名——包括你在迁移走的 legacy 路径,那里改名反而把迁移破了。
如何判断:回看 prompt——没列具体文件就是边界隐式、Claude 自由扩。
2. Agent 沿 import / call 链扩到邻居文件
Claude 在 service.ts 改名,然后更新 8 个 call site。每处都是合法触达,但累计 9 倍于你预期。
如何判断:diff 有「核心」文件改动 + 很多小 call site 更新。每条必要,但合起来就是扩 scope。
3. Agent 自发做了一致性 pass
Claude 注意到一半文件用 import type 一半没用,改名时顺便”协调”了 6 个文件——一句没问。
如何判断:diff 里有和改名无关的改动(import 顺序、格式、命名)——纯 creep。
4. CLAUDE.md / prompt 授权了「顺手清理」
# CLAUDE.md
- 看到机会就改进代码质量
- 相关工作中顺手修明显问题
看着无害的规则,是 scope 扩张的常设授权。
如何判断:grep -i "improve\|clean up\|while you're\|opportunit" CLAUDE.md——每条匹配都是 creep 授权。
5. 重构本质就跨多文件
某些重构真的跨整 codebase(改公开 API、迁 state 库)。scope 是真的——bundle 进一个 PR 才是问题,应该拆。
如何判断:diff 本身真的必要——bug 是「一个巨型 PR」而不是「PR 链」。
6. Agent “修测试” 顺势级联
核心改动后测试挂了,Claude 在多个测试文件里更新 expectation、mock、fixture。有些合法(签名匹配),有些是 mask 失败。
如何判断:测试文件改动跟着改名——审一下哪些是签名更新、哪些是悄悄削弱断言。
最短修复路径
按收益从高到低。前 2 步在 prompt 层把 agent 关起来。
Step 1:在 prompt 里钉死可编辑文件清单
可编辑文件(其他都不许动):
- src/services/user.ts
- src/services/user.test.ts
任何其他文件:
- 必须动就停下来问。
- 不要做一致性 / 格式 / 清理改动。
「停下来问」让 Claude 能 flag 真级联而不私自扩。
Step 2:相邻问题导向 FOLLOWUPS.md
可编辑清单外发现问题:
- 不要修。
- 在 `FOLLOWUPS.md` 加一行描述问题 + file:line。
- 这些变 future task,不进本 PR。
Claude 仍能”有帮助”但不扩 scope——每个发现都成文档化的 future task。
Step 3:审 prompt + CLAUDE.md 找 creep 授权
grep -in "improve\|clean up\|while you're there\|opportunistically" \
CLAUDE.md src/**/CLAUDE.md prompts/*.md
每条换成更紧的:
- task scope 外不要动。
- 相邻问题进 FOLLOWUPS.md,不进本 PR。
Step 4:收到过宽 PR 时回退 scope 外文件
不要原样 merge、也不要全拒——回退 scope 外文件:
git diff --stat HEAD
# 找出 scope 外的文件
git checkout HEAD -- <scope 外文件>
# 重跑测试,确认核心改动还好
pnpm test
留要的、删不要的——PR 缩回预期 scope。
Step 5:真的多文件重构就拆 PR 链
改名真要更新 8 个 caller 就阶段发布:
PR 1:加 `findUser` 与 `getUser` 共存(不动 caller)。
PR 2:迁 `src/services/billing/` 的 3 个 caller。
PR 3:迁 `src/api/` 的 5 个 caller。
PR 4:删 `getUser`。
每个 PR 5 分钟 review。总 diff 同,但可 review 性高 10 倍。
Step 6:核查测试有没有被削弱
diff 里的每个测试文件:
# 单独 diff 测试文件
git diff -- src/services/user.test.ts
# 找:被删的 expect/assert、新加的 .skip、放宽的类型检查
git diff -- src/services/user.test.ts | grep -E '^-.*expect|^-.*assert|^\+.*skip'
断言被删 / 测试被 skip——“修测试”其实是回归被伪装。还原测试,重 prompt 让它修生产代码。
预防建议
- 每个代码 prompt 显式可编辑文件清单 + 越界「停下来问」
- 维护
FOLLOWUPS.md让 Claude 能浮 surface 邻近问题不动手 - 审 CLAUDE.md 删「顺手改进」常设授权
- 真多文件重构拆 PR 链,不要一个巨型 PR
- 核查测试 diff——删断言 / 加 skip 是伪装成修法的 creep
- review 时对照原 scope,不只看最终 diff——diff 本身可能看着合理
相关阅读
- Claude Code 改错文件
- Claude Code Plan 模式偏离计划
- Claude Code 覆盖既有改动
- Claude Code 新手入门
- Claude Code 工作流
- Claude Code 项目配置
标签: #排查 #Claude Code #排查 #Scope 蔓延