Claude Code Plan 模式偏离计划:6 个 scope creep 来源 + 把 plan 当合同

你批准 5 步 plan,Claude 做了 7 步、跳了 1 步、顺手 refactor 旁边代码——用锚到文件的步骤 + 每步停下汇报,把执行绑回批准的 plan。

你按 Shift+Tab 进 plan 模式,Claude 提了一份 5 步 plan,你批了。执行开始。最后:D、E 文件被改了(不在 plan 里)、step 3 因”现有代码已经做了”被跳了(其实没做)、step 4 偷塞了个”为清晰度做的小 refactor”。PR 比 plan 大,你从 diff 看不出哪些改动是授权的。

最快修法: plan 模式是一个只读的调研步骤,不是执行合同。批准 plan 只是把会话切到某个编辑模式(auto、accept-edits、或逐条 review),并让 Claude 开始干——它会把 Claude 钉死在那几步上。要把执行绑回来:(1) 让每个 plan 步骤指名确切的文件和符号;(2) 批准前,追加一句”不许超出本 plan、每步后停下”的指令。这两处改动,才让 plan 变成能约束 Claude 的东西。

常见原因

按命中率从高到低:

#原因最快判别
1Plan 太松——步骤指概念、不指文件步骤写「the billing flow」,无路径
2prompt 里有「顺手改」隐含授权prompt 含「也修」「清理」「顺手」
3Claude 中途静默改了 plandiff 和批准的步骤对不上 1:1
4步骤太粗(「为清晰度 refactor X」)一行抽象、无具体操作
5执行中发现真 edge case 静默纳入单步 diff 触达它没提的文件
6CLAUDE.md 常设「看到问题就改进」授权grep -i "improve|clean up|opportun" CLAUDE.md 命中

1. Plan 太松、Claude 推了额外活儿

Plan 写”更新 billing flow”——没说哪些文件。Claude 改了 5 个文件包括你没预期的 2 个。Plan 没钉路径,任何合理解读都被授权。

如何判断:回看批准的 plan——步骤引用概念(「the billing flow」)而非文件路径——Claude 就有空间扩。

2. 原 prompt 有「顺手改」隐含授权

你说「修 bug 看到问题清理一下」。「和清理」就是 scope creep license。Claude plan 修 bug,执行时扩到清理(plan 里没显出来)。

如何判断:在 prompt 里搜「顺手」「也修」「清理」「改进一下」——任意一个都授权了 plan 越界。

3. Claude 中途静默改了 plan

plan 写 5 步,执行到中间 Claude 自己判 step 3 没必要就跳了——也没说。或者沿路看到坏的东西自己加了 step 6。这是被反复提报的已知行为——见 Claude Code 关于 plan-mode 遵守度的开放 issue(例如 issue #41062#10398)。

如何判断:实际 diff 和批准 plan 逐步对比——不一致 = 静默 plan 更新。

4. Plan 步骤太粗

「Step 4:把 billing.ts 重构成新模式」——具体什么意思?步骤没规定,Claude 解读宽泛。

如何判断:步骤是 1 行抽象(「为清晰度 refactor X」)而不是具体操作(「在 billing.tsprocessPayment 改名 processCharge,仅此」)。

5. 执行中发现 edge case

Claude 跑 step 2 时发现一个真 edge case。不是停下来问,是把修法纳入这一步——diff 比 plan 大了。

如何判断:单步 diff 触达的文件 / 关切超出步骤描述——edge-case 顺手扫了。

6. CLAUDE.md 授权了机会主义改进

某些 CLAUDE.md 写「看到问题就改进代码质量」——这是常设授权,凌驾于 plan 边界。在 auto 模式下尤其要命:classifier 会直接读你的 CLAUDE.md 内容并当成意图。

如何判断grep -i "improve\|clean up\|opportun" CLAUDE.md 返回让 Claude 扩 scope 的规则。

plan 批准到底是怎么回事(2026 年 6 月)

懂了机制就明白为什么「我批了啊」不够。按 Shift+Tab 循环切模式:default(每次编辑前问)-> acceptEdits -> plan。状态栏显示当前模式,plan 模式下底部显示 plan mode on。plan 模式里 Claude 只读文件、跑只读 shell 命令,然后把 plan 写到 ~/.claude/plans/(在 settings.jsonplansDirectory 可把 plan 留在仓库里)。plan 能跨 /clear 和 context 压缩保留。

你批准时,Claude 弹一个对话框。截至 2026 年 6 月,选项是:

  • Approve and start in auto mode(批准并进 auto 模式)
  • Approve and accept edits(批准并自动接受编辑)
  • Approve and review each edit manually(批准并逐条人工 review)
  • Keep planning with feedback(带反馈继续 plan)
  • Ultraplan 精修(浏览器里 review)

不管选哪个,批准都会退出 plan 模式、把会话切到那个编辑模式,然后 Claude 开始干。这个流程里没有任何东西逼 Claude 一字不差地执行那些步骤——这个约束只能来自 plan 文本本身和你的执行指令。两个后果:

  • 如果 showClearContextOnPlanAccept 开着,每个批准选项还会先问你要不要清掉 planning context。你说 yes,那么任何只在聊天里说过、但没写进 plan 的规则就没了。把约束放进 plan 里,别只放在对话里。
  • auto 模式下,classifier 会尊重你在对话里说的边界(「不要动 plan 外的文件」),但如果压缩丢了说这句话的那条消息,边界就可能失效。要硬保证,请改用 deny 规则。

最短修复路径

按收益从高到低。Step 1 + 2 把 plan 变成可强制合同。

Step 1:plan 步骤具体 + 锚到文件

能用的 plan 步骤指名文件、符号、精确改动——不是抽象:

差:「Step 3:为清晰度 refactor billing 逻辑。」
好:「Step 3:把 `processPayment` 在 src/billing.ts:42 改名 `processCharge`,更新 3 处 call site(src/api/checkout.ts:87、src/jobs/billing-sweep.ts:23、src/services/refund.ts:55)。无其他改动。」

具体步骤有可验证结果——抽象步骤无法”偏离”因为它本身就模糊。批准前,按 Ctrl+G 在编辑器里打开 plan,把任何指概念而非路径的步骤收紧。

Step 2:执行时绑「不许越界」

还在 plan 模式时,让 Claude 把下面这段折进 plan 里(这样批准时清 context 也丢不掉),然后再批准:

仅执行上面批准的 plan。
- 不要加步骤。
- 不要跳步骤;觉得没必要就停下来问。
- 不要动 plan 里没显式列的文件。
- plan 外发现问题在末尾列 TODO,不要修。

每步后:
1. 贴一行总结说改了什么。
2. 停,等 "proceed"。

「每步停等」是合同强制。批准时,第一次测试一个 plan 稳不稳,选 review each edit manually(别选 auto 模式)。

Step 3:执行中 diff 实际 vs plan

每步后批准下一步前:

git diff --stat HEAD

对照步骤的显式文件清单——多出来的文件 = scope creep。回退 + 重 prompt:

你的 step 3 动了 D 和 E,plan 里没有。
撤掉那些改动。step 3 只动 src/billing.ts。

Step 4:拒静默 plan 更新

Claude 宣布「我打算跳 step 3 因为…」或「我加个 step 6 处理…」:

plan 变更要我批准。停。
说你打算改什么、为什么。
我要么更新 plan、要么你按原计划继续。

在宣布阶段 catch 偏移,不在代码落地后。如果已经偏了,按 Shift+Tab 退回 plan 模式、从当前状态重新 plan——一个高杠杆习惯是在 CLAUDE.md 写一行:「If execution diverges from the approved plan, stop and re-enter plan mode(执行偏离批准的 plan 就停下、重进 plan 模式)」。

Step 5:审 prompt + CLAUDE.md 找 scope-creep 授权

搜并删常设越界授权:

# 在 prompt / CLAUDE.md 里
grep -in "while you're there\|also fix\|opportunistically\|improve where you see" \
  CLAUDE.md src/**/CLAUDE.md

每条换成更紧的:「只修指定的,其他问题 flag 成 TODO」。

Step 6:重复偏移就用 deny 规则锁死 + 一步一 commit

CLAUDE.md 指令是强力提醒,不是墙。如果 Claude 老往同一个 plan 外目录跑,在 .claude/settings.json 加一条硬 deny 规则,让那里的编辑无论什么模式都被挡:

{
  "permissions": {
    "deny": ["Edit(src/legacy/**)"]
  }
}

然后逼 Claude 每步一 commit,每个 commit 的 diff 就是可审计单位:

每步后:
1. git add <仅本步文件>
2. git commit -m "step N: <一行总结>"
3. 停

commit 后 `git status` 还有未 stage 改动——就越界了。

每步一个干净 commit log——“你照 plan 了吗”就可验证。

如何确认修好了

  1. git log --oneline 显示每个批准的步骤一个 commit,没有多余。
  2. git diff --stat <plan 起点 commit> HEAD 只列出 plan 里指名的文件。
  3. 每个 plan 步骤的 diff 只触达该步指名的文件——没有 edge-case 顺手扫、没有「来都来了」refactor。
  4. 如果用了 auto 模式,/permissions -> Recently denied 里没有你没打算要的 plan 外编辑。

预防建议

  • plan 步骤指名 文件 + 符号 + 具体操作,永不写”refactor for clarity”
  • 把「不许超出本 plan」规则折进 plan 文本,别只放聊天里,免得批准时清 context 把它丢了
  • 一个新 plan 第一次跑,批准时选 review each edit manually,别选 auto 模式
  • CLAUDE.md / prompt 模板找常设 scope-creep 授权,删掉
  • CLAUDE.md 加一行「If execution diverges from the approved plan, stop and re-enter plan mode」
  • 绝不能动的目录,用 deny 规则——唯一不受模式影响的硬保证
  • 一步一 commit,审计轨迹原生在 git 里

常见问题

批准 plan 会逼 Claude 一字不差照步骤走吗? 不会。批准会退出 plan 模式、把会话切到某个编辑模式(auto / accept-edits / 逐条 review),然后 Claude 开始干。它不是绑定合同。步骤被遵守的程度,取决于它写得多具体、约束得多紧——所以锚到文件的步骤加一句「不许越界」才关键。

批准后我的 plan 规则不见了——去哪了? 如果 showClearContextOnPlanAccept 开着,某个批准选项会先清 planning context,把你在聊天里说过、但没写进 plan 的内容一并丢掉。把约束写进 plan 文本里,或在 settings.json 关掉这个设置。

Claude 跳了一步,说代码”已经做了”。怎么拦? 在执行指令里加:「不要跳步骤;觉得某步没必要就停下来问。」然后每步用 git diff 验证——被跳的步骤会表现为:对照 plan 列出的文件清单,少了一处应有的改动。这种静默跳步行为在 Claude Code 的 issue 里有记录。

能不能把 plan 模式设成默认,免得我忘了按 Shift+Tab? 能。在 .claude/settings.json"permissions": { "defaultMode": "plan" },或用 claude --permission-mode plan 启动。会话会以只读开始,直到你批准一个 plan。

auto 模式老改我说过不许动的文件——为什么不听? auto 模式的 classifier 从 transcript 读边界,所以一条边界只在说它的那条消息还在时有效。压缩之后可能丢。重说一遍,或者更好——在 settings 里加 deny 规则,让限制不受模式或 context 状态影响地强制生效。

怎么在 Claude 跑之前改 plan? plan 显示时,按 Ctrl+G 在默认编辑器里打开它,把步骤收紧(补文件路径、删模糊措辞),保存,然后再批准。

相关阅读

标签: #排查 #Claude Code #排查 #Plan 模式