AI 编码 agent 在循环、不进展怎么办

Agent 一直试同一个修法,或在两种状态之间反复横跳。30 秒内识别循环、用一句话打断它。

典型的”agent 死循环”长这样:Claude Code 或 Cursor agent 跑了 12 轮 npm test,每轮都改同一处 expect(...).toBe(...)、跑、失败、再改回去;或者 Aider 在 git diffgit restore 之间来回横跳。屏幕上 token 一直在烧,进度条没动。

最快的修法(不到一分钟):先把 agent 停下来(Claude Code 按 Esc,Cursor 或 Codex 点停止按钮)。让它把最近 10 步动作列成一张表,然后发一句约束,明确说清楚不要碰什么、下一步只做哪一件事——比如 Don't touch the test. The test is correct — change the implementation to match.(不要动测试,测试是对的,去改实现让它符合测试)。这一句话就能打破大多数循环。

大多数时候,模型并不是”不会修”,而是陷进了一个反馈坏掉的小闭环:测试 flaky、约束不够、或它读到的上下文和磁盘上的代码已经对不上了。这篇讲怎么 30 秒内识别循环、怎么用一句话打破它,以及怎么从一开始就预防。

先判断你属于哪一类

动作轨迹里的现象最可能的原因对应修法
不动代码,同一测试也会绿-红-绿测试 flakyStep 3
改动在两个不相关文件之间横跳Prompt 有歧义Step 2
同一计划步骤卡了 5 轮以上计划错了但不肯重新规划Step 2
长会话里反复”修”已经修好的错误上下文被旧 log 塞满Step 4
mock / stub 反复被加上又删掉mock 和真实实现来回打转Step 2
重跑一条已经失败过的命令工具失败但 agent 没识别到Step 1
同一处改动反复套用,文件里出现重复代码块(Cursor)编辑器以为改动失败了见下方 Cursor 说明

常见原因

按命中率从高到低排序。

1. 它一直跑的测试本身是 flaky

最常见的循环触发器:测试里有时间戳、随机数、网络请求或 Date.now(),导致同样的代码每次跑结果不一样。Agent 看到红,改一处;变绿;下一轮又红——它以为是自己改坏了,于是回滚——又绿——再改——又红。

Run 1: pass
Run 2: fail (snapshot mismatch — timestamp drift)
Run 3: pass (after agent "reverted")
Run 4: fail

如何判断:把 agent 停下来,你自己手动连跑 3 次同一条命令。如果不动代码也能复现”绿-红-绿”,问题就在测试,不在 agent。

2. Prompt 有歧义,agent 在两种解读之间反复横跳

你说”修复登录 bug”,但代码里有 OAuth 和 magic link 两条路径。Agent 改完 OAuth 看到 magic link 测试挂,转头改 magic link,OAuth 又挂——它在两个互斥目标之间反复横跳。

如何判断:看 agent 最近 5 步动作的 diff,如果改的是两个不同文件的不同函数,并且每次改完都”撤销上一次”,就是 prompt 太宽。

3. 计划写错了但 agent 不肯重新规划

Agent 一开始写了个”先改 schema,再改 API,最后改 UI”的计划。Schema 那步其实走不通(比如数据库已经有数据),但 agent 死守计划,只在 schema 这一步反复改 migration 文件,不肯回头重新规划。

如何判断:让 agent 输出当前 plan + 现在卡在第几步。如果它说”还在 step 2”超过 5 轮,就该手动让它重新规划。

4. 上下文窗口被旧错误塞满,agent 看不到全局

Claude Code 或 Codex 在长会话里,上下文里大部分都是”上一次失败”的 log,agent 一直在拟合那些旧错误,看不到当前代码已经变了。Claude Code 在接近上下文上限时会自动 compact,但在一个反复磨的任务上,那份摘要仍可能把错误的假设带下去。

如何判断:开个新会话,把当前代码贴进去,重新描述目标。如果新会话一次就改对了,就是上下文污染。

5. Agent 在 mock 和真实实现之间打转

它写了一个 mock 让测试过,下一轮发现 mock 不真实,去改真实实现,结果原来的测试又挂,于是回去改 mock——循环。

如何判断:搜 diff 里有没有 jest.mockvi.mockMagicMocksinon.stub 反复出现。

6. 工具调用失败但 agent 没意识到

Bash 工具返回了非零 exit code,但 agent 把它当 stdout 解析,以为命令”成功了只是输出不对”,于是再跑一遍。Cursor 的 Agent mode 有一个已知的变种:它套用了一次改动,但负责 apply 的模型报告这次编辑失败,于是 agent 重试——有时会把同一段代码粘两遍,留下重复代码。

如何判断:看最近 3 次工具调用的 exit code,如果都是非 0 但 agent 没在 chat 里提”命令失败了”,就是它没识别到失败。在 Cursor 里,打开它正在改的那个文件,看有没有重复的函数或 import。

最短修复路径

按收益排序。前两步通常 60 秒内就能打破循环。

Step 1:立刻停下,回看最近 10 步动作

Esc(Claude Code)或点停止按钮(Cursor / Codex)。在 Codex CLI 里,后台命令可以用 Ctrl-C 中断,且会保留它的最终输出和 exit status。不要让它继续烧 token。然后在聊天里要它输出最近的动作总结:

列出你最近 10 步做了什么操作(文件路径 + 改动摘要 + 命令 + 结果)。
不要解释,只要表格。

90% 情况下你会立刻看到它在两个文件之间来回切,或在同一行反复改。

Step 2:用一句话约束打破循环

最有效的反循环 prompt 模板:

停。不要再改 X。
当前真实情况是 Y。
下一步只做一件事:Z。
不要碰其他文件。

具体例子:

循环类型约束 prompt
反复改测试期望值”不要再动测试。测试是对的,去改实现让它符合测试。“
反复加 / 删 mock”保留真实实现。把 mock 全删了,跑集成测试看真实失败信息。“
反复改 schema migration”schema 不动。回到 step 1,从只改 UI 层开始。“
在两个文件之间横跳”只允许编辑 src/auth/login.ts,其他文件全部只读。”

如果 agent 已经改得一团乱,先把代码回滚到干净状态再继续。在 Claude Code 里,在空输入框上连按两次 Esc(或运行 /rewind)打开 checkpoint 菜单——Claude 在每次编辑前会自动建 checkpoint,你可以选 Restore code(只回滚代码)、Restore conversation(只回滚对话)或两者都回滚。注意 checkpoint 只记录通过 Claude 文件编辑工具做的改动;被 rmmv 等 bash 命令改动的文件不会被记录,这类操作请用 git 找回。

Step 3:把测试或验证条件钉死

如果 agent 一直在追一个 flaky 测试,先把测试稳定下来再让它继续:

# 连跑 5 次同一个测试,看是否稳定
for i in {1..5}; do npm test -- --testNamePattern="login flow"; done

如果有 2 次失败,先 mock 时间 / 网络 / 随机源:

// vitest setup
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-01-01'));

测试稳定后再让 agent 继续修。

Step 4:开新会话,喂干净上下文

如果以上都不行,最后一招是 reset 上下文。在 Claude Code 里,做完全重启之前还有两个更省事的选项:

  • /clear 清空当前会话的对话历史(你的 CLAUDE.md 和项目文件不受影响)。
  • /compact 把目前为止的对话总结成摘要、替换掉完整历史。你可以给它方向:/compact Focus on the failing auth test and the current implementation.

如果会话已经污染得太严重,就干脆重开:

  1. 复制当前文件的完整内容(不是 diff)。
  2. 开新会话。
  3. 用这个模板重新启动:
目标:[一句话目标]
当前代码(已经改过 N 轮):
[贴完整文件]
当前失败的测试 / 错误:
[贴完整错误]
约束:不要改测试,不要改配置,只允许改 src/X.ts。
先输出你的计划,等我批准再动手。

新会话不会被旧错误污染,往往一次就过。

Step 5:设硬性 iteration cap

让循环不要再发生。Claude Code 可以在 CLAUDE.md 里加:

## Agent 行为约束
- 单个任务最多 5 轮 build/test 迭代
- 第 3 轮还没过,主动停下输出现状让用户决策
- 不允许在同一个文件的同一段代码上反复改 ≥ 3 次

其他 agent 也是同样的思路,但各自读的文件已经变了——写错地方,你的规则就会悄悄不生效:

Agent规则文件放哪里(截至 2026 年 6 月)别再用的旧名字
Claude CodeCLAUDE.md(项目根目录)
CodexAGENTS.md(从根目录往下读,离工作目录最近的那份优先)
Cursor.cursor/rules/*.mdc(Project Rules).cursorrules 已废弃,且在 Agent mode 里会被直接忽略
AiderCONVENTIONS.md,用 aider --read CONVENTIONS.md 或在 .aider.conf.yml 里写 read: 加载.aiderrules 并不是 Aider 真正认的文件

怎么确认循环真的被打破了

别信”看起来好了”。走开之前先验证:

  1. 你自己把目标命令连跑两遍:两次都是同样的绿,说明反馈信号现在稳定了。
  2. 看 agent 最后一次的 diff,确认它只改了你允许的文件——没去碰你冻结的测试或配置。
  3. 在 Cursor 里,扫一遍被改的文件,看有没有重复的函数或 import(apply 循环 bug 会留下这些)。
  4. 让 agent 复述完成标准并确认已满足(比如”npm test -- auth 全过”),然后你再亲手把那条命令跑一遍。

预防建议

  • 在对应 agent 的正确规则文件里设 iteration cap(见上面的表),比如”同一个测试连挂 3 次就停下问人”。
  • 不只看 agent 最终输出,要看它的 reasoning / action trace,循环模式往往要看 trace 才发现。
  • 把 flaky 测试隔离到单独的 flaky.test.ts,别让 agent 在不稳定的反馈下迭代。
  • 长任务每 5 轮强制 agent 重新输出”当前 plan + 当前进展”,或者跑 /compact 把上下文压瘦。
  • 给 agent 明确的”完成标准”——不要说”修好登录”,要说”npm test -- auth 全过”。
  • 出现循环就 reset 上下文(/clear 或开新会话),不要试图在污染的上下文里继续。
  • 在 Cursor 里,如果 agent 卡在某一次编辑上死循环,关掉 Auto 模型选择、手动指定一个具体模型;还不行就复制 Request ID 上报。

常见问题

为什么 agent 总把自己改对的修法又撤回去? 几乎都是测试 flaky。修法是对的,但下一轮测试因为无关原因(时间戳、网络、随机种子)挂了,agent 把它读成”我把别的改坏了”,于是回滚。先按 Step 3 把测试稳定下来,再让它继续。

Claude Code 改乱了一堆,怎么撤回? 在空输入框上连按两次 Esc,或运行 /rewind,选一个 checkpoint,选 Restore code。checkpoint 在每次编辑前自动创建。它不覆盖被 bash 命令改动的文件,所以凡是 rm/mv 动过的,用 git 找回。

.cursorrules 还是约束 Cursor agent 的正确做法吗? 不是。截至 2026 年,单个 .cursorrules 文件已废弃,而且在 Agent mode 里会被直接忽略——而循环恰恰就发生在这个模式下。规则请改放到 .cursor/rules/*.mdc

上下文满了,该用 /clear 还是 /compact 想继续干同一个任务、只是想要更轻的历史,就用 /compact(可以加一句聚焦说明)。要切到一个不相关的任务、想要干净起点,就用 /clear。如果是单个任务被污染得很严重,最可靠的重置是开一个全新会话、把当前文件完整贴进去。

agent 说命令”成功了”但其实什么都没变,为什么? 多半是它把非零 exit code 误读成了正常输出。检查最近几次工具调用的 exit code。你自己把命令再跑一遍;如果确实失败,把真实错误贴回去,并明确告诉 agent 上一条命令失败了。

相关阅读

标签: #AI 编程 #排查 #排查