09:Fallback & Recovery:Claude Code 如何在失败后继续推进
上一篇把 Observability 理解成 Agent 的证据层。
09:Fallback & Recovery:Claude Code 如何在失败后继续推进
上一篇把 Observability 理解成 Agent 的证据层。
工具调用、权限裁决、错误、成本和上下文变化留下轨迹以后,失败就会从屏幕上的一段报错,变成可以继续判断的材料。它会带着证据回到 Agent Loop:刚才尝试了什么,哪里失败了,失败类型是什么,还有没有更合适的下一步。
所以 09 要接着追:
Claude Code 遇到失败以后,怎样决定是换路继续,还是回到一个可继续的位置重新走?
这里我会把 Fallback & Recovery 分成两条线:
- fallback 关心“当前这一步失败以后怎么换路继续”。
- recovery 关心“整段任务走坏以后怎么回到可继续的位置”。
前者靠失败分类和下一步策略,后者靠 checkpoint、transcript、diff 和任务状态。

这篇文章的主线
08 讲的是“留下证据”。09 讲的是“使用证据”。
一次失败回来以后,Agent 需要看到一组能继续判断的信息:
- 失败发生在哪一步。
- 失败属于哪一类。
- 有没有新的证据。
- 当前路线还能不能继续。
- 要不要换工具、换参数、问用户、回到 checkpoint,或者停下来。
所以这篇先把重点放在两条失败处理路径上。retry、fallback、checkpoint、rewind、rollback、resume 都有用,只是它们处在不同层级。几篇源码解析资料也把失败处理放回工具执行层和 runtime 主循环里看:工具层负责把错误包装成可读结果,主循环再决定下一步怎么走[1],[2]。
这篇只抓两条主线:
-
fallback。
这一步失败了,怎样用失败证据换一个动作继续。 -
recovery。
这一段任务走坏了,怎样回到一个还能继续判断的位置。
fallback 和 recovery 的区别
fallback 和 recovery 都发生在失败之后,但它们处理的失败尺度不一样。
| 机制 | 关心的问题 | 常见动作 |
|---|---|---|
| fallback | 当前这一步失败以后怎么继续 | 重试、换参数、读更多证据、缩小范围、问用户、停止 |
| recovery | 当前路线走坏以后回到哪里 | checkpoint、rewind、恢复 diff、保留失败摘要、重新规划 |
fallback 更像局部换路。
比如 pnpm test 失败了,下一步可以读取失败堆栈;路径不存在,下一步可以搜索文件;命令参数错了,下一步可以修正参数再调用。
recovery 更像任务级恢复。
比如 agent 改了一大串文件以后发现方向错了,或者连续几轮修改让测试失败更多,这时继续在坏状态上修补会越来越乱。更稳的做法是回到某个 checkpoint,把文件和会话状态拉回去,再带着失败摘要换一条路线。
这两个层级分清以后,09 就不会变成术语堆叠。
fallback 解决“下一步怎么走”,recovery 解决“回到哪里再走”。
fallback:先判断失败类型
fallback 的第一步是分类。
同样是失败,下一步可能完全不同。网络抖动可以短暂重试,参数错误要改参数,权限拒绝要换低风险路径,测试失败要读错误输出,上下文不足要补证据。
可以先把失败分成几类:
| 失败类型 | 典型信号 | 更合适的方向 |
|---|---|---|
| 临时错误 | 网络抖动、外部服务短暂不可用 | 短暂重试,限制次数 |
| 参数错误 | 路径不存在、命令参数不对、schema 校验失败 | 修正参数,重新调用 |
| 测试失败 | 断言失败、快照不匹配、编译报错 | 读取失败输出,定位原因 |
| 权限拒绝 | 动作被 permission / settings / hook 拦住 | 换低风险路径或询问用户 |
| 上下文不足 | 缺少关键文件、约束丢失、判断开始漂移 | 搜索、读文件、整理状态 |
| 方向错误 | 连续失败、改动变大、目标偏航 | 更新 plan 或进入 recovery |
这一步看起来朴素,但很关键。
如果 Agent 把所有失败都当成“再试一次”,就容易原地打转。测试失败需要定位原因;权限拒绝需要换低风险路径或询问用户;路径错了,需要先修正路径。
所以 fallback 的重点是让每次尝试都带着新的判断。
再选择下一步策略
失败分类以后,Agent 才能选择策略。
常见的下一步大概有几种:
-
retry。
适合临时错误,比如网络抖动、短暂超时、外部服务偶发失败。retry 要有限制,也要有退路。 -
换参数。
适合路径、命令、schema 这类问题。先修正输入,再调用同一个工具。 -
读更多证据。
适合测试失败、构建失败、上下文不足。先读错误输出、相关文件、日志或配置,再决定改哪里。 -
缩小范围。
适合大任务开始失控。先修一个测试、一个模块或一个更小的目标。 -
询问用户。
适合权限、需求、外部系统、业务判断不清楚的情况。这里问的是决策,要把已经观察到的证据和风险一起交代清楚。 -
停止并说明。
适合连续失败、证据不足或风险过高。成熟的 agent 要能讲清已经试过什么、卡在哪里、下一步需要什么。
这里最容易被滥用的是 retry。
retry 只是 fallback 的一种策略。它只有在“下一次尝试有机会因为外部状态变化而成功”时才合理。对于测试失败、参数错误、权限拒绝和方向错误,重复执行通常只是消耗时间和上下文。Blake Crosley 提到的 frustration detection 和 compact failure circuit breaker,也可以放在这个方向理解:系统需要识别原地打转,并在必要时换策略或停下来[3]。
一个简单原则是:
重试应该带着新信息发生。
如果没有新信息,就换观察路径、换策略,或者停下来说明。
permission denied 是特殊失败
权限拒绝要单独讲。
它和网络超时、命令报错不一样。permission denied 更像边界信号。07 里已经说过,权限裁决会给下一轮判断增加一个事实:这条路现在走不通,或者需要用户明确裁决。Haseeb Qureshi 的源码摘记里也把 permission denied 放在回流到下一轮判断的线索里[4]。
比如 agent 想读 .env,被拒绝了。更合适的 fallback 是换一条低风险路径:
- 读
.env.example、README 或配置 schema。 - 查看配置加载代码。
- 根据报错推断缺失字段。
- 让用户提供脱敏后的关键信息。
- 如果真实值确实必要,讲清原因和风险,请用户决定。
命令也一样。
curl | sh 被拒绝以后,可以改成阅读安装说明、固定下载来源,或者让用户手动执行。rm -rf dist 被拒绝以后,可以先列出目标目录、说明清理范围,再请求确认。
这里的重点是尊重边界。
安全系统是任务执行的一部分。权限拒绝处理得好,Agent 还能继续推进;处理不好,它就会在同一个被拒绝动作上反复撞墙。
recovery:回到可继续的位置
fallback 处理的是局部失败。recovery 处理的是任务级走坏。
长任务里经常会出现这种情况:单步看都说得过去,但整条路线已经偏了。
比如:
- 修一个 bug 时顺手重构了太多文件。
- 测试越修失败越多。
- 前面已经约定不动数据库结构,后面却开始改模型字段。
- 用户看完结果后说“回到刚才那个版本,我们换一种做法”。
这时更需要回到一个可继续的位置,继续换小动作修补只会让状态更乱。
Claude Code 的 recovery 可以理解成几类材料配合:
| 材料 | 回答的问题 |
|---|---|
| checkpoint | 文件和会话可以回到哪个点 |
| transcript | 这次任务走到哪里,为什么走到这里 |
| diff | 哪些文件被改过,改动范围有多大 |
| 失败证据 | 刚才为什么失败,哪条路线已经尝试过 |
| 任务状态 | 回去以后应该保留哪些约束和判断 |
checkpoint 解决“能回到哪里”。transcript 和 diff 解决“回去时要恢复什么”。失败证据和任务状态解决“回去以后别再踩同一个坑”。几篇 rewind / checkpoint 实践资料主要支撑的就是这一点:恢复既要看文件,也要看会话时间线和恢复后的新路线[5],[6]。
所以 recovery 更像把任务时间线拉回一个安全点,同时保留刚才学到的东西。
checkpoint 还需要任务状态
checkpoint 很重要,但它只是 recovery 的底座。
只有文件回滚时,Agent 回去以后可能忘记为什么要回滚。
只有 transcript 时,回放以后也很难还原真实仓库。
两者都有但缺少失败摘要时,Agent 可能会从同一个地方再摔一次。
所以 recovery 还需要任务状态。
这个任务状态至少要包括:
- 当前目标是什么。
- 用户有哪些硬约束。
- 已经读过哪些证据。
- 哪些路线尝试过。
- 哪些失败说明方向不对。
- 哪些文件改动应该撤掉。
- 回到 checkpoint 后下一步换什么策略。
一个更稳的 recovery 流程可以是:
- 选择 checkpoint,恢复文件和会话位置。
- 从 transcript 里找到刚才为什么失败。
- 保留一段失败摘要。
- 标记已尝试路线,避免重复。
- 从恢复点继续,但换观察路径或实现方案。
这比普通 undo 更适合 Agent。文件状态回去了,有价值的学习结果留下来。下一轮可以带着失败证据重新判断。
例子:测试失败别原地打转
用测试失败把两条线串起来。
用户说:
“帮我把这几个测试修好。”
第一次运行 pnpm test 失败,这通常还在 fallback 范围内。Agent 应该先看失败证据:
- 失败的是哪个测试。
- 错误类型是断言失败、类型错误、环境错误,还是快照变化。
- 失败堆栈指向哪些文件。
- 当前修改是否和失败区域有关。
接下来可以选择策略:
- 如果是命令参数问题,修正参数。
- 如果是环境问题,先读配置或问用户。
- 如果是断言失败,读最小相关代码。
- 如果是快照变化,确认是预期变化还是回归。
修改以后再次运行同一组测试。第二次结果很关键。
- 失败减少了,说明方向可能有效。
- 失败类型变了,说明系统状态被推动了,需要重新分类。
- 失败完全一样,说明修改没有触达原因,要换观察路径。
- 失败更多了,说明改动可能扩大了问题,需要考虑回滚。
如果连续几轮都没有进展,或者修改已经偏离目标,就进入 recovery。此时更合理的做法是回到最近的 checkpoint,撤掉坏改动,同时保留失败摘要:
已尝试路线:修改 A 模块以修复 test-x。
结果:失败类型没有变化,并额外引入 test-y 失败。
下一步:回到 checkpoint,改为先阅读 B 模块和测试 fixture。
这个摘要很重要。它让 recovery 之后的 Agent 不会回到过去再走同一条错路。
所以测试失败里的关键是:每一次失败都要改变下一步判断。
对我写 Agent 的启发
写到这里,我会把失败处理拆成两个对象,避免塞进一个大而全的异常里。
第一个是 FailureEvent。
它描述这次失败本身:
| 字段 | 作用 |
|---|---|
| failure_type | tool_error、command_failed、permission_denied、test_failed、context_missing、direction_wrong |
| attempted_action | 刚才尝试了什么 |
| evidence | 错误输出、权限结果、diff、测试摘要或上下文线索 |
| recoverability | transient、recoverable、needs_user、terminal |
| retry_count | 同类失败已经发生几次 |
| next_options | retry、read_more、change_params、ask_user、rewind、stop |
第二个是 RecoveryPoint。
它描述任务可以回到哪里:
| 字段 | 作用 |
|---|---|
| checkpoint_id | 可以恢复到哪个点 |
| transcript_ref | 对应哪段会话时间线 |
| diff_summary | 这个点之后改过什么 |
| failure_summary | 为什么要回到这里 |
| preserved_lessons | 回滚后仍要保留的判断 |
| next_plan | 回到这里以后换什么策略 |
这样设计以后,fallback 和 recovery 的职责会更清楚。
FailureEvent 帮 Agent 判断这一步怎么换路。
RecoveryPoint 帮 Agent 判断任务走坏后回到哪里。
我现在对 Fallback & Recovery 的理解可以收成一句话:
fallback 让失败变成下一步策略,recovery 让走坏的任务回到可继续的位置。
参考资料
Claude Code 源码解析与恢复机制
-
[1] Claude Code 源码架构分析(含可以启动的源码本地部署)- GitCode
本文主要参考它关于工具执行层如何做错误分类、安全化日志、失败结果处理和进度消息的分析。 -
[2] Claude Code 终端 Agent Runtime 全解析(非常详细),泄露源码 - GitCode
本文主要参考它从 runtime 角度对长任务稳定执行和恢复能力的分析。 -
[3] What the Claude Code Source Leak Reveals - Blake Crosley
本文主要参考它关于 frustration detection、compact failure circuit breaker 和防止 agent 原地打转的分析。 -
[4] Inside the Claude Code source - Haseeb Qureshi
本文主要参考它关于 permission denied 如何作为下一轮输入的线索。
Checkpoint 与恢复实践
-
[5] How to Use Claude Code /rewind to Roll Back Conversations and Code to Any Checkpoint - MindStudio
本文主要参考它关于/rewind如何把会话和代码状态回退到 checkpoint 的实践说明。 -
[6] Checkpoints and Rewind - claude-howto
本文主要参考它关于 checkpoint、rewind、恢复分叉和比较替代方案的实践流程。
下一篇继续追的问题
这一篇把 Fallback & Recovery 拆成了两条线。
核心结论是:
fallback 处理局部失败后的换路,recovery 处理任务走坏后的恢复。
到这里,Claude Code 已经能计划动作、裁决动作、执行动作、记录动作,也能在失败后继续或恢复。
下一篇要看的是另一种能力:把外部工作流和可复用能力接进来。
真实工程里,Agent 需要自己会循环、会恢复,也要接团队已有的规则、工具、自动化和外部系统。格式化、审计、通知、MCP、slash command、skill、hook,都会把 runtime 打开一个入口。
所以 10 要继续追的问题是:
Claude Code 如何通过 hooks、skills、slash command 和 MCP,把外部工作流接进 Agent Runtime?