Claude Code 改错文件:6 种命名混淆 + 用 allow + deny 清单防御

改了你没让改的文件——同名 / 大小写差 / import 链外扩——git revert + 在 prompt 里加 allow / deny 清单消歧。

你说「改 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.tsLogin.tsauth.service.tsauth.test.tsuser.tsusers.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.tspackages/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.tslogin.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 #排查 #误改