Claude Code 第 8 次跑 pnpm test。每次:改同一行 expect(...).toBe(...)、重跑、又挂、回退。或者它在「修 auth bug」的两种解读间反复横跳——修 OAuth 挂 magic-link、修 magic-link 挂 OAuth。Token 烧着、进度条不动。Agent 进了循环。
绝大多数 loop 都不是「模型修不了」,而是反馈环坏了:flaky 测试、prompt 没约束住、context 被污染、工具失败没被察觉。修法不是换更强的模型,而是 30 秒内识别 loop 签名,用一句话约束或一个按键打破它。下面是 6 种 loop 形态、各自的破解约束,以及恢复时要用到的确切 Claude Code 命令(Ctrl+C、双击 Esc / /rewind、/clear、/compact)。本文以 2026 年 6 月的 Claude Code 为准,模型为 Claude Opus 4.7 / Sonnet 4.6。
TL;DR——60 秒打破一个 loop
- 立刻中断。 按
Esc停掉当前动作;命令卡死就按Ctrl+C。别等 loop「跑完」——它不会停。 - 读 trace。 让 Claude 把最近 10 个动作列成表格(文件 + edit + 命令 + exit code)。规律几乎都是两个文件交替、或同一行被反复改。
- 套上对应签名的一句话约束(见下表)。
- 如果 context 被污染,双击
Esc(或跑/rewind)回滚到干净的 checkpoint,或/clear后用一句话目标重启。 - 预防下一次:在
CLAUDE.md里设迭代上限,再配一个Stophook。
常见原因
按命中率从高到低:
1. flaky 测试——过 / 挂 / 过 / 挂
最常见触发。测试用了 Date.now()、网络调用、随机数、snapshot 时间戳——同一份代码一次过一次挂。Agent 看到红 → 改 → 绿 → 重跑又红 → 回退 → 绿 → 改 → 红,没完没了。
如何判断:停掉 agent(Esc)。你自己不改代码连跑 3 次同一命令。能复现绿红绿——是测试的问题,不是模型那次 edit 的问题。
2. 模糊 prompt——agent 在两种解读间漂
「修 login bug」——但你有 OAuth 和 magic-link。Agent 修 OAuth,magic-link 测挂;切 magic-link,OAuth 测挂——在互斥的两种解读间反复跳。
如何判断:看最近 5 个 diff——动了两个不同 feature 的两个不同文件、每个新 edit 撤上一个——prompt 没约束住。
3. Plan 错了 agent 拒绝 replan
agent 锁定「改 schema → 改 API → 改 UI」。Step 1 不可能(表有数据,列删不掉)——agent 死磕 step 1 反复重写 migration,而不是 replan。
如何判断:问「你当前 plan 是什么?现在第几步?」——5+ 轮都说”还在 step 1”,强制 replan。
4. context 被旧 error 塞满
长 session 里,context 窗口大半都是过期的「上次失败」日志——agent 在拟合一堆不再反映现状的旧 error。一个明显信号是你看到 Autocompact is thrashing: the context refilled to the limit...——这是 Claude Code 在自动 compact 成功后、context 又被某个大文件或工具输出立刻填满时给出的提示。按 Anthropic 的 troubleshooting 文档,当 compact 后 context 连续三次被重新填满到上限,Claude Code 就会停止重试,正是为了避免在毫无进展的 loop 上白烧 API 调用。罪魁通常是 agent 每一轮都重读同一个超大文件、或一段巨大的工具输出。
如何判断:跑 /context 看窗口占用百分比(拆解里会显示 system prompt、tools、CLAUDE.md、消息历史各自吃了多少 token)。超过 ~70% 还卡着,多半就是 context 的问题。再用 /clear(或开新 session)贴当前代码、重述目标确认一下——第一次就成,就是被污染了。
5. Agent 在 mock 和实 implementation 间漂
写 mock 让测试过,下轮觉得 mock 不真实改实 impl,原测试挂,回 mock——loop。
如何判断:跨次 diff 里搜 jest.mock / vi.mock / MagicMock 被加被删。
6. 工具失败 agent 没察觉
Bash exit code 非 0。Agent 把 stdout 当成功,又跑同命令——loop 因为”失败信号”它看不见。
如何判断:看最近 3 个工具调用的 exit code——非 0 但 Claude 没说”命令失败”——它漏了。
最短修复路径
按收益从高到低。Step 1 + 2 在一分钟内打破大多数 loop。
Step 1:停 agent,让它列最近 10 个动作
先中断。Esc 停掉当前动作、回到提示符;Ctrl+C 取消卡死的命令。一次按不下来就再按——第一次是温和请求,连按会升级到强制结束。要是终端彻底没反应,关掉它,在同一目录下跑 claude --resume 把 session 接回来;重启不会丢对话。
停下来后问它:
列最近 10 个动作(文件路径 + edit 总结 + 命令 + exit code)。
不要评论,只表格。
大多数时候你会看到两个文件交替、同一行反复改、或测试反复跑却没代码进展——这个 pattern 就揭示了 loop 签名。
Step 2:按签名套约束句
匹配 loop 类型和约束 prompt:
| Loop 类型 | 约束 prompt |
|---|---|
| 反复改测试期望 | 「不要动测试。测试是对的——改 implementation 匹配它。」 |
| 反复加/删 mock | 「保留真实现,删所有 mock,跑集成测试。」 |
| 反复改 migration | 「schema 冻结,重新 plan 只动 UI 层。」 |
| 来回两个文件 | 「只允许动 src/auth/login.ts,其他全只读。」 |
| 同一 flaky 测试反复挂 | 「停跑这个测试,mock 时间依赖,继续。」 |
| 重跑失败命令 | 「上条命令 exit 非 0。读 error,不要盲重跑。」 |
Step 3:flaky 测试先稳
不要让 agent 在 flaky 信号上 loop——先稳:
# 跑 5 次看稳不稳
for i in {1..5}; do pnpm test -- --testNamePattern="login flow"; done
2+ 次挂就 mock 不确定性源:
// vitest setup
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-01-01'));
然后让 agent 在稳定的测试上继续。
Step 4:重置 context——rewind、clear 还是 compact
当 trace 被一堆过期失败日志占满时,你有三档递进的杠杆,按想保留多少状态来选:
| 命令 | 作用 | 何时用 |
|---|---|---|
双击 Esc 或 /rewind | 打开 rewind 菜单。每个 prompt 都是 checkpoint,可单独恢复对话、代码或两者。 | 想撤掉 loop 的 edit、从一个已知正常点重试。 |
/compact [focus] | 把历史总结后替换,保留要点。可带 focus,如 /compact keep only the plan and the diff。 | context 很大,但目标/plan 仍然正确。 |
/clear | 彻底清空对话历史。你的代码改动会保留——/clear 不会回退文件。 | 整个 session 都被污染了,想要一张白纸。 |
依赖 /rewind 前有两点要先知道(据 Anthropic 的 checkpointing 文档,2026 年 6 月)。第一,只有在输入框为空时,双击 Esc 才会打开 rewind 菜单;如果你已经打了字,第一下 Esc 是清空文字、第二下才打开菜单。第二,checkpoint 只追踪 Claude 自己的 edit 工具改过的文件——被 Bash 命令改动的文件(rm、mv、某个脚本、一条往磁盘写的 migration)不会被记录,rewind 也撤不回来。这类改动只能靠 Git 兜底。checkpoint 会跨 session 保留,30 天后自动清理。
/clear 之后(或在全新 session 里),用一段自包含的紧凑 prompt 重启:
目标:[一句话]
当前代码:[贴整个文件,不是 diff]
失败测试/错误:[原样贴]
约束:不动测试,只动 src/X.ts。
先打出 plan,等批准再 edit。
干净的 context 不会被旧失败日志牵着走,通常一次就过。另外,/rewind 里选「只恢复代码」会回退文件改动但保留对话——讨论有用、但 edit 跑偏时很顺手。
Step 5:设迭代上限
预防下一个 loop。软约束是在 CLAUDE.md 里写一条规则:
## Agent 行为约束
- 每个任务最多 5 次 build/test 迭代
- 3 次失败后停下汇报
- 同一文件的同一块不要 edit 超过 3 次
- 任何工具调用重试 3 次后停下来问
Cursor 用 Project Rules / .cursor/rules、Codex 用 AGENTS.md——同理。不过 CLAUDE.md 里的规则只是建议性的,agent 能给自己找借口绕过去。要硬停就加一个 Stop hook:Stop 事件在 Claude 回完一轮时触发。
这里方向特别容易搞反,要看清楚(据 hooks 文档,2026 年 6 月):对 Stop hook 来说,exit code 2 和 {"decision": "block"} 是阻止 Claude 停下、强迫它继续——和你想要的 loop 护栏正好相反。要在上限处真正停掉 session,得往 stdout 输出 {"continue": false, "stopReason": "iteration cap reached"}(exit code 是几都行)。写个小脚本读迭代计数、超过上限就打印 {"continue": false, ...},你就有了一个模型无法绕过的护栏。千万别用 exit 2 去停 loop——它的效果是反的,会让 agent 继续跑。
Step 6:卡 plan 时强制 replan
agent 永远 step 1:
停。当前 plan 不行。不要继续。
打印:
1. 为什么 step 1 不行
2. 两个替代方案
3. 推荐一个
等我批准再继续。
强制 agent 跳出失败 plan,不是在上面磨。
怎么确认 loop 真的断了
loop 可能这一轮看着好了、下一轮又接上。走人之前,先查三件事:
- trace 在往前走。 再让它列一遍最近 10 个动作的表格。你该看到新文件、新行,而不是同一个 edit 反复。同一行被改了 3 次以上——还在 loop。
- 信号是确定的。 你自己手动把目标检查连跑 3 次。如果不是稳定绿或稳定红,那你什么都没稳住——flaky 测试还在给 agent 喂噪声。
- 完成标准被字面满足。 不是「看起来好了」——你当初给的那条确切命令 exit 0。自己跑一遍
pnpm test -- auth(或你指定的那条),亲自确认 exit code。
三条都成立,loop 才算断。任何一条没过,你离下一个 loop 只差一个 prompt。
预防建议
- 在
CLAUDE.md设迭代上限,再用Stophook 兜底——给 loop 一个内建、模型无法绕过的出口。 - 把 flaky 测试隔离到
flaky.test.ts——agent 永远不会在不稳定信号上迭代。 - 同一步骤连续失败 3 次就强制 replan。
- 给出显式完成标准——不要「修 login」,而是「
pnpm test -- authexit 0」。 - 看 agent 的动作 trace,不只看最终输出——loop 在结果暴露之前,早就显形在 trace 里了。
- loop 一出现,就双击
Esc回到干净 checkpoint、或/clear重启,别去救污染的 session。 - 盯着
/context;超过 ~70% 就主动/compact,免得任务中途撞上 auto-compact thrashing 那堵墙。 - 如果 thrashing 是某个巨大文件引起的,让 agent 按行号区间读、别整个读,或者把这部分活儿交给一个跑在独立 context 窗口里的 subagent。
FAQ
Claude Code 卡在循环里,怎么停下来?
按 Esc 停掉当前动作,或按 Ctrl+C 取消卡死的命令。一次不行就再按——连按会升级到强制结束。终端彻底卡住的话,关掉它,在同一目录下跑 claude --resume 接回同一 session。
/clear、/compact、/rewind 有什么区别?
/clear 清空对话历史,但保留你的代码改动。/compact 把历史总结后替换(可带 focus,如 /compact keep only the plan and the diff)。/rewind(或双击 Esc)打开 checkpoint 菜单,可把对话、代码、或两者恢复到更早的某个 prompt。撤坏 edit 用 /rewind,给仍然有效的 session 瘦身用 /compact,想不丢文件地重新开始用 /clear。
Claude Code 会自动建 checkpoint 吗?
会。截至 2026 年 6 月,你发的每个 prompt 都会生成一个 checkpoint,双击 Esc(或 /rewind)就能回滚到其中任意一个——只恢复对话、只恢复代码、或两者都恢复。
为什么同一个 flaky 测试会让 agent 反复 loop?
flaky 测试给 agent 一个不确定的过/挂信号,于是它改、看到绿、下一轮又红、回退——没完没了。先把测试稳住(mock 掉时间/网络/随机,比如 vi.useFakeTimers()),再让它继续。绝不要让 agent 在一个会自己变的信号上迭代。
能不能硬性限制迭代次数,让 loop 根本发不出来?
能。CLAUDE.md 里的规则只是建议,但 Stop hook 由 Claude Code 强制执行:迭代计数超过上限时输出 {"continue": false, "stopReason": "..."},agent 就无法再往下走。这里别用 exit code 2——对 Stop hook 来说,exit 2(以及 {"decision": "block"})会强迫 Claude 继续,正是你想停掉的那个 loop。能停掉 session 的只有 {"continue": false}。
相关阅读
外部参考:
标签: #排查 #Claude Code #排查 #Agent 死循环