你说「改 src/auth/login.ts」。Claude 改了 src/auth/logIn.ts(大小写不一样——你忘了存在的过期文件)。或者改了 src/auth/login.test.ts 以为 bug 在那。或者改了 src/legacy/auth/login.ts 因为名字匹配它先找到。
改错文件来自两种失败模式:命名歧义(多个候选匹配描述)或 scope drift(agent 沿 import / call 链超出你给的范围)。两种修法一样:prompt 加显式 allow / deny 清单 + 发生时 git revert + 重 scope。
常见原因
按命中率从高到低:
1. 两个相似名文件——agent 挑错的
login.ts 和 Login.ts、auth.service.ts 和 auth.test.ts、user.ts 和 users.ts——没显式消歧,agent 按文件扫描里先浮出来的挑。
如何判断:find . -iname "login*" 返多个候选——没路径”login”就是歧义。
2. Agent 沿 import 链外扩
你说「修 billing.ts」。它读 billing.ts 看到 import { format } from "../utils"——判断问题在 utils.ts 就去改了。
如何判断:diff 里有 utils/ / lib/ / common/ 的文件,尽管你的 task 是某 feature 特定的。
3. Agent 顺手”清理”邻居文件
改目标的同时注意到隔壁文件有 typo 或不一致就”修”——邻居文件进了 diff。
如何判断:diff 里有 2-3 个目标附近文件的小不相关改动——单看无害,合起来就是 creep。
4. Monorepo / 重复目录的命名歧义
apps/web/src/auth/login.ts 和 packages/shared/auth/login.ts 都存在——agent 按工具调用里先出现的路径挑。
如何判断:diff 在你预期的另一个 package 里——同文件名、不同祖先目录。
5. Agent 当成生产代码改了测试文件
login.test.ts 的 mock 调用 login()——agent 看到 mock setup 当成生产,改了 mock 不是真 implementation。
如何判断:尽管你要的是生产修法,diff 在 .test.ts / .spec.ts 里。
6. case-insensitive 文件系统混淆
macOS(case-insensitive)上 Login.ts 和 login.ts 是同文件。Linux CI(case-sensitive)上是两个——agent 的 edit 可能落在错的 canonical 形式,取决于平台。
如何判断:路径里同内容、大小写不同——git log --follow 看有没有改名。
最短修复路径
按收益从高到低。Step 1 恢复,Step 2 防再发。
Step 1:git status + git diff 盘点损失
# 改了什么
git status
# 详细 diff
git diff --stat
git diff
每个非预期文件回退:
# 单文件
git checkout HEAD -- src/wrong/file.ts
# 多文件
git checkout HEAD -- src/wrong/ src/another-wrong/
# 已 commit 了
git revert <commit-hash>
Step 2:重 prompt 带显式 allow + deny 清单
**只**编辑这些文件(其他都不许):
- src/auth/login.ts
**不要**动这些(即便你觉得需要改):
- src/auth/Login.ts(deprecated,不要复活)
- src/auth/login.test.ts(测试文件,独立关切)
- src/legacy/(整个目录禁止)
- src/utils/(共享,独立批准)
task 需要编辑其他任何文件就停下来问。
deny 清单是关键——它 catch allow 清单 catch 不到的「agent 以为它在帮忙」。
Step 3:prompt 用绝对路径,不要「那个 auth 文件」
差:「修 auth 文件」
好:「修 `apps/web/src/auth/login.ts`」
完整路径消除命名解析歧义——monorepo 里始终包含 workspace 前缀。
Step 4:歧义文件名审 + 收敛
repo 里有多个相似名:
# 找相似名
find . -iname "login*" -not -path "*/node_modules/*"
# 解决:删过期的、改测试名等
git rm src/auth/Login.ts # 真死了的话
mv src/auth/login.test.ts src/auth/login.spec.ts # 消歧
命名整洁的 codebase 改错文件少。
Step 5:危险区域用 feature 分支 + worktree
# agent task 用新分支
git checkout -b feature/auth-fix
# 或独立 worktree 隔离
git worktree add ../project-agent feature/auth-fix
agent 改错文件就整个分支 / worktree 扔掉,不影响 main。
Step 6:CLAUDE.md 写「不许动」默认
agent 永远不该改的文件:
## Agent 不许动的文件
- `migrations/*.sql`——历史记录,不许改
- `legacy/`——冻结
- `*.snap`(snapshot)——靠测试重跑更新,永不手改
- `pnpm-lock.yaml`——靠 `pnpm install` 更新,永不直接编辑
- `dist/`、`build/`——生成的,不许改
- `.env*`——密钥,永不改
task 像是要动这些就停下来问。
常设规则 catch 每 task allow/deny 漏掉的。
预防建议
- prompt 用绝对路径不用描述——「src/auth/login.ts」不是「auth 文件」
- 每个非琐碎 prompt 带 allow + deny 文件清单
- CLAUDE.md 写 migration / snapshot / lockfile / 生成产物的常设「不许动」
- 季度审 repo 找混淆的同名 / 大小写差异,收敛
- 风险 agent 工作走分支 / worktree——回退一条命令搞定
- monorepo 始终带 workspace 前缀消跨包名碰撞
相关阅读
标签: #排查 #Claude Code #排查 #误改