Claude Code CLI 2.1.235 韧性与恢复:Claude Code 出错后如何继续,又有哪些动作无法撤销¶
先跟一条失败链走到底¶
用户让 Claude Code 修改一个文件,再调用远端发布工具。模型还没有返回任何工具调用时,第一批 API 请求先失败了。本版 Probe 观察到的 model 序列是:
request 1 claude-sonnet-4-5 -> HTTP 529
request 2 claude-sonnet-4-5 -> HTTP 529
request 3 claude-sonnet-4-5 -> HTTP 529
request 4 claude-haiku-4-5 -> 200
前三次失败发生在模型请求阶段,完整 tool_use 还没有产生,因此此时没有文件可恢复,也没有远端动作可撤销。变化的只有同一逻辑请求的 attempt 记录和最终选用的模型:HTTP 重试由请求层负责,主模型耗尽后是否切备用模型由 fallback 策略负责。它们不会因为多发了三次 HTTP 请求,就把 Agent 的正常工作自动算成四个新 turn。
第四次请求成功后,工具循环才真正改变外部状态。把后续两轮消息写开,区别会很清楚:
assistant: tool_use(id=edit_1, name=Edit,
input={file_path:"checkpoint.txt",
old_string:"CHECKPOINT_ORIGINAL",
new_string:"CHECKPOINT_MODIFIED"})
user: tool_result(tool_use_id=edit_1): File updated successfully
assistant: tool_use(id=deploy_1, name=Deploy,
input={release:"recovery-demo"})
user: tool_result(tool_use_id=deploy_1):
{"deployment_id":"dep-42","status":"accepted"}
edit_1 返回成功后,文件系统里已经是 CHECKPOINT_MODIFIED;deploy_1 返回 accepted 后,远端系统已经拥有 dep-42 这条状态。假设随后 assistant 流中断并触发 model fallback,客户端可以 abort 尚未完成的工具,并把这次未稳定的 assistant/tool 分支标成 tombstone。tombstone 的 owner 是消息图:它决定哪段 provisional 消息不再进入主分支。它不拥有文件系统,也不拥有远端服务,所以 checkpoint.txt 不会自动变回原字节,dep-42 也不会自动消失。
恢复后的下一次请求还可能因为历史、工具结果和附件太大而得到内部 Prompt is too long。在符合后文资格检查时,Agent Loop 先扣住这条用户可见错误,只允许当前逻辑 turn 做一次 reactive compact:
Prompt is too long
-> reactive compact
-> summary + preserved message groups + regenerated attachments
-> compact boundary
-> rebuild the same logical turn
reactive compact 改写的是 context builder 为下一次 API 组装的 active message view。它可以把较早消息换成 summary,并按本路径保留近期合法消息组;compact 本身不会重新执行 edit_1 或 deploy_1,也不会回滚两者,但重建请求后的模型仍可能再次提出同一动作。某条旧 tool_result 之后是原样保留还是只剩摘要,属于上下文恢复问题;避免重复写入仍要依靠 tool ID、幂等键和外部状态回读。
到这里,各种状态已经有了各自的 owner:request runner 拥有 HTTP attempt,Agent Loop 拥有正常 turn 与恢复预算,message graph 拥有 provisional 消息与 tombstone,context builder 拥有 active message view,filesystem 拥有当前文件字节,checkpoint store 保存可回退的旧字节和引用,远端服务拥有 deployment。恢复动作只有作用在正确 owner 上才有意义。
上面把两类运行证据和一条边界示例接成同一条可读链:sonnet × 3 -> haiku 来自 settings-resilience.json;CHECKPOINT_ORIGINAL -> CHECKPOINT_MODIFIED -> CHECKPOINT_ORIGINAL 来自 checkpoint-rewind.json。dep-42 是用来说明远端所有权的明确示例,不是 Probe 声称执行过的真实部署;本章没有用客户端证据冒充远端回滚证明。
Owner 确定后,再选择恢复层¶
| 恢复层 | 恢复对象 | 保留什么 | 丢弃/替换什么 | 不能撤销什么 |
|---|---|---|---|---|
| HTTP/stream retry | 同一逻辑模型请求的 attempt | 稳定请求身份和已确认状态 | 失败连接或不完整 attempt | 已执行工具和远端写入 |
| Model/output recovery | 模型选择或未完成 assistant 输出 | 已稳定消息、计数与工具结果 | provisional assistant、失败模型分支 | 已完成 side effect |
| Context recovery | 可发送的消息表示 | 任务目标、合法后缀、boundary | 远期历史的原始表示 | 真实文件/服务状态 |
| MCP/tool recovery | 工具目录、调用结果和执行状态 | generation、tool ID、结构化错误 | 旧 schema 或失败调用状态 | server 已接受的非幂等动作 |
| Session/file recovery | transcript 逻辑历史或受管文件 | 持久事件、checkpoint 字节 | 当前逻辑分支或文件版本 | 旧进程、数据库、远端事务 |
后文按这五类对象展开,并用 terminal reason、request count、tool ID 和外部状态检查说明“恢复成功”到底指什么。
同一条失败链里,七类“重来一次”不能混算¶
| 恢复动作 | 重做对象 | 是否增加 Agent turnCount |
工具是否可能重复 | 典型触发 |
|---|---|---|---|---|
| HTTP/API retry | 同一模型请求 attempt | 通常不增加 | 尚未产生工具时不会 | 529、连接/服务错误、retryable status |
| stream -> non-stream fallback | 同一逻辑模型轮次的传输方式 | 不增加正常工具轮次 | 取决于失败前是否已有完整 tool block | |
| model fallback | 换模型重新完成当前轮次 | 不一定增加 | 已完成 side effect 无法自动撤销,存在重复风险 | |
| max_tokens recovery | 从截断 assistant 内容继续生成 | 独立 recovery counter | 通常不重跑已配对工具 | |
| malformed tool retry | 修复 stop_reason/tool block 不一致 | 独立一次性恢复 | 未形成完整可执行 block 时不执行 | |
| reactive compact retry | 压缩后重做过长请求 | 不等同正常 next turn | 请求前未执行工具时无副作用 | |
| Stop hook re-entry | 带 hook feedback 进入下一模型轮次 | 增加 | 后续模型可能选择新动作 |
如果监控只统计 HTTP 次数,会把这些机制混在一起;如果 maxTurns 把所有 attempt 都算成 turn,又会让网络抖动提前耗尽 Agent 工作预算。2.1.235 为正常轮次、输出恢复、reactive compact、thinking-only nudge、Stop hook block 分别维护状态。
故障恢复总图¶
build request
|
+-- context too long ----------> reactive compact -> rebuild request
|
v
send/stream model request
|
+-- retryable transport/status -> retry same logical call
+-- stream unsupported/fails --> non-streaming fallback when allowed
+-- model unavailable/refusal --> model/refusal fallback policy
|
v
parse assistant blocks
|
+-- max_tokens ---------------> continuation prompt, bounded counter
+-- stop=tool_use but no block -> malformed retry once
+-- thinking only ------------> visible-answer nudge once
|
v
execute tools
|
+-- validation/permission fail -> error tool_result, model can revise
+-- runtime error/abort -------> structured result + cleanup
+-- MCP disconnect -----------> tool error / reconnect / refresh later
|
v
decide stop/continue
|
+-- Stop hook blocks ---------> feedback + next model turn, cap 8
+-- maxTurns reached ---------> terminal max_turns
+-- success ------------------> completed
+-- unrecoverable ------------> typed terminal reason
|
v
persist transcript/checkpoint/telemetry
请求层:先判断错误是否值得重试¶
可重试不等于所有错误都重试¶
请求层会区分状态码、provider、beta、模型可用性、cache-control、流式能力和连接状态。典型类别包括:
- overloaded/529、部分 server error;
- stale connection、idle/stall/watchdog;
- 网络超时或连接中断;
- provider 不支持某 beta/字段;
- cache-control rejection;
- model blocked/unavailable;
- rate limit;
- malformed response;
- auth、permission 或不可恢复 4xx。
身份/参数错误如果机械重试,只会放大延迟和流量。相反,短暂 overload 可以按 backoff 重试或切 fallback model。版本报告不能写一个没有分支证据的统一“重试 3 次”,因为不同路径的次数、delay、consent 和 provider fallback 不同。
关联一次逻辑请求¶
可靠诊断需要同时记录 client request ID、server request ID、query/turn、attempt、model、provider、stop reason 和 timing。这样可以区分:
- 用户的一轮 Agent 工作;
- 同一轮内部的多个 HTTP attempts;
- fallback 后不同 serving model;
- 子 Agent 自己的 retry;
- 工具运行期间的 progress。
详见 遥测专题。
流式恢复:部分内容怎样处理¶
content block 是恢复边界¶
流式 parser 维护 message/content block 生命周期。只有完整且可解析的 tool_use block 才进入 streaming tool executor。半截 JSON 不能执行,这个边界能减少“网络中断时执行一个参数不完整的写操作”。
文本和 thinking 可以逐步显示,但是否能在 fallback 后保留,需要看该分支的 salvage/supersedes 语义。不能因为 UI 已显示部分文本,就假定它已经成为下一轮稳定 transcript。
Stream 转 non-stream¶
当 provider/连接不适合继续流式,而 non-streaming fallback 未被禁用时,客户端可以用非流方式重新请求。这个动作恢复的是传输,不是新的 Agent 计划;它不应自动增加正常 turnCount。
如果流失败前已经完成并启动一个 tool block,风险会显著提高:重新请求可能让模型再次生成同一动作。客户端会 abort/tombstone provisional 状态,但无法撤销已完成外部副作用。因此工具幂等和结果去重比“能 fallback”更重要。
模型 fallback:消息能丢,副作用不能丢¶
本版 Agent Loop 至少区分:
| fallback | 触发 | 客户端动作 | 风险 |
|---|---|---|---|
| 普通 model fallback chain | overload、server error、model unavailable 等 | tombstone 当前 provisional assistant/tool state,abort 未完成工具,换模型 | 已完成工具可能在新模型重试时重复 |
| refusal fallback | 模型拒绝,且策略/用户同意允许换模型 | 处理部分 refusal/text,再切换或结束 | 需要保留用户选择和拒绝原因 |
| server mid-stream serving fallback | 流中服务模型变化 | 按 allowlist/supersedes 处理 block 和模型身份 | usage、文本归属和模型权限需重新确认 |
这三行内部仍有 visible、silent、server 三条 refusal lane。visible lane 可以弹用户 dialog;缺少 dialog 能力、remote-controlled session 或设置关闭会改变可选路径。silent lane 使用一次性 fallback credit,并可在条件满足时 salvage 已显示的 partial text;server lane 先检查目标模型是否在 availableModels allowlist,拒绝时丢弃响应并保留原 session。只有 swapSession=true 的 server fallback 才改 mainLoopModel/mainLoopModelForSession;只有被 tombstone 的响应含 tool use 时才执行工具 sweep。不能把 server fallback 概括成“统一切 session、统一 abort 全部工具”。
拒绝切换或用户取消时,客户端 tombstone provisional assistant/tool result,清空本次执行器并处理 credit forfeiture;接受 visible fallback 时才把 session model 改到目标模型。silent continuation 可能把 salvage text 与新模型首个 text block 拼接,并用 supersedesUuids 标记被替代消息。服务端目标不在 allowlist 时不会继续使用它,即使事件已经声明了 toModel。
Tombstone 的准确含义¶
tombstone 告诉 message graph/UI:“这段 provisional 消息不再是有效主分支的一部分”。它可以避免失败模型输出继续污染下一轮,但不具备以下能力:
- 把写过的文件恢复到原内容;
- 撤销 Git commit/push;
- 取消已经完成的 HTTP/MCP 写请求;
- 收回已经发出的消息;
- 恢复外部数据库事务。
未完成工具可以通过 abort controller 尝试取消。已经完成的动作需要 file checkpoint、工具补偿 API、幂等键或人工回滚。
模型输出层的三条自修复路径¶
先理解 withholding:错误发生了,但用户出口可以暂时扣住¶
2.1.235 对 prompt-too-long 和 max-output 使用一条容易被忽略的控制语义:底层先生成 internal assistant error/sentinel,Agent Loop 把它放进内部候选消息用于恢复判断,但暂时不向 outward stream yield。恢复成功时,用户看到 compact boundary、summary 或续写后的结果,而不是中间错误;恢复预算耗尽时,最后一个错误才 surface。
这不等于“错误在系统里完全消失”:
- prompt-too-long 的低层 API error 可抑制普通CLI/Teleport首错,但OTEL
api_error和span failure仍可记录;只有底层确实发生多次attempt时才另有api_retries_exhausted,且最终是否到collector仍受exporter/content gate控制; - compact progress、hook 和 boundary 仍可进入 UI/SDK;
- max-output 前已经流出的普通 text 不会被这条 sentinel 自动收回;
- 最终失败仍形成 typed terminal reason。
因此准确说法是:用户可见错误被延迟,观测和内部状态没有被抹掉。 过滤点见 reverse/javascript/cli.readable.js:272019-272038,prompt-too-long 的观测分流见 232158-232183。
max_tokens continuation¶
模型流正常结束但 stop_reason=max_tokens 或 model_context_window_exceeded 时,transport 生成 internal apiError=max_output_tokens sentinel。CLI 最多恢复 3 次。普通路径保留已经产生的 assistant/error 视图,加入隐藏 continuation 提示,要求直接从中断处继续、不道歉、不复述;独立 recovery count 不增加正常工具轮次。
还有一条 feature-gated incomplete-thinking 路径:响应必须恰好是一个可恢复的 signed thinking block,stop reason 为 max_tokens 且模型兼容。命中后不追加普通 continuation prompt,而是保留 trailing thinking、设置 resumeIncompleteThinking,并给后续 assistant 标记 resumedFromIncompleteThinking。这条路径保护 signed thinking 协议连续性;本版 gate 的本地 override 不可达,所以应标为静态可达合同,不应声称精确二进制默认已触发。
这里有一个必须明确纠正的社区误读:这个恢复分支不会把 max_tokens 从 8K 自动提升到 64K。 recovery state 把 maxOutputTokensOverride 留为 undefined;每次请求重新按 model config、remote config 和 env 解析并夹在上限内。恢复的是输出连续性,不是自动扩大模型预算。证据见 272215-272224、409474、410242-410245。
用户影响:长回答或长代码不一定在第一次 max_tokens 就结束,但每次恢复都会增加请求、延迟和 token;前面已经流出的文字仍可能被用户看到。3 次仍截断时才 yield 最后的 sentinel,并以 API error terminal 结束。版本比较要记录 continuation 上限、是否 signed-thinking 恢复、消息拼接/去重和实际 max_tokens 解析,不能只搜索“64K”常量。
Malformed tool use¶
API 返回 stop_reason=tool_use,但客户端没有得到完整可执行 block 时,本版只重试一次。默认 false 的 tengu_malformed_tool_use_clean_retry 决定消息视图:legacy 保留首次 assistant 并追加纠错提示;clean 先 tombstone 首次 assistant,只用原历史加 clean prompt 重试。第二次仍不一致才返回 malformed_tool_use_exhausted。
关键保障:第一次没有完整 block 就不执行工具,避免对半截参数产生副作用;纠错是有界的,避免模型永久重复错误协议。
Thinking-only nudge¶
当响应以 end turn/stop sequence 结束且只有 thinking、没有用户可见文本,CLI 会补一次提示要求给出可见答案。第二次仍无可见文本则接受结束。这个分支改善 UI 完整性,但不应无限消耗 turn。
以上三条位于 Agent Loop 无工具结束判断中,见 reverse/javascript/cli.readable.js 271474-271481、271983、272160-272264、409342、409739;恢复次数常量 DGS=3 见 272456。
Context 恢复:请求太长时先缩小问题¶
Prompt-too-long:先扣住 context-overflow error,再决定能不能压缩¶
主模型调用显式传入promptTooLongIsHandled:true。常见400/413,以及任何命中PTL/context-overflow文本或typed marker的error,都会统一映射成internal assistant errorPrompt is too long;变量名isWithheld413不能被解释成只处理HTTP 413。证据见271834-271842、323913-323918、324195-324263。
Agent Loop 不会对所有 PTL 无条件 compact。收到被扣住的错误后,它按下面顺序决定:
- Rapid-refill breaker:若已经连续 3 次在上次 compact 后不足 3 个 turn 又触顶,直接 surface thrashing 文案,避免继续烧 summary 请求。
- 能否压缩:合法 message group 少于 2 时,压缩没有意义;客户端还会比较 conversation estimate 是否占请求至少 80%,告诉用户主要是历史本身,还是 system/tools/attachments。
- 恢复资格:auto-compact 未禁用、不是 compact 自己、不是被排除的辅助 query(已有可复用 precomputed swap 是例外)、远端 gate允许、未 abort、此前未尝试。
- 先 precomputed,后 reactive:检查准备好的 swap;否则运行 PreCompact Hook并发起一次 reactive summary。
- 成功:outward stream只发boundary、summary、attachments/hooks;preserved messages留在重建后的active history,不向外重复发一遍。随后transition=
precomputed_compact_swap或reactive_compact_retry,同一个逻辑turn重新请求。 - 失败:除rapid-refill breaker外,Hook block、summary API失败、gate不满足或此前已经尝试时不再无限重试,最终yield原错误;若有compact error detail,再追加
automatic compaction failed: ...。Breaker则surface新的thrashing文案并以rapid_refill_breaker终止。
核心判断位于216020-216054、216268-216277、232809-232820、261884-261890、272185-272213。成功路径中用户可能看见compact progress/boundary,但看不到最初的PTL assistant error;普通恢复失败得到context/compact终态,rapid-refill则得到专用thrashing终态。
Precompute 与 reactive compact¶
正常路径会在到达 blocked line 前预计算 compact;如果模型请求仍因 context/image 过长失败,Agent Loop可以做一次 reactive compact后重建请求。hasAttemptedReactiveCompact 防止同一轮无限压缩重试。这里的“一次”是当前 PTL恢复预算,不代表 summary内部为处理自身 PTL所做的有界 group-drop retry。
compact 恢复的是“下一次请求能装下并继续”。它不能保证摘要包含所有被替换细节,所以 summary prompt、重新生成的 attachments、tool discoveries 和 logical parent 是可靠性的组成部分。当前 /compact、reactive、partial 和 precomputed 路径还保留合法消息组;cold/full auto 或特定 SDK full compact 才返回 messagesToKeep: []。
Auto-compact 关闭¶
本版 release delta 明确改进了 auto-compact 关闭且达到上限时的错误:提示用户到 /config 重新启用,而不是只报抽象 context error。这个变化看似微小,实际把失败从“不可操作”变成“有明确恢复动作”。
Prompt cache 不是 context recovery¶
cache miss 增加输入写入成本和 prefill 延迟,但不直接释放窗口;compact/microcompaction 才改变活跃 token。恢复调查需要同时看 cache_read_input_tokens 与 context composition,不能把 cache miss 当作窗口溢出原因。
详见 上下文治理与多层缓存。
工具失败:把错误变成下一轮观察¶
工具错误一般不需要终止整个 Agent Loop。以下失败会转换为结构化 error tool_result:
- 工具不存在;
- JSON/schema/custom validation 失败;
- PreToolUse/permission deny;
- 用户取消;
- sandbox/command/path/network 错误;
- MCP auth/transport/server error;
- tool implementation exception;
- output schema/hook error。
结果保留原 tool_use_id,让模型知道哪一个动作失败、为什么失败,再决定缩小参数、换工具、请求用户批准或停止。
Error result 与 retry 的边界¶
CLI 不应在不知道语义的情况下自动重跑任意工具。读操作通常更适合 retry;写操作需要幂等键或确认外部状态。模型看到 error 后重新发起同名工具,是新的 Agent 决策,不等于底层透明 retry。
Hook 恢复:可编程控制也需要熔断¶
Pre/Post hook 失败¶
hook timeout、非零 exit、非法 JSON 或 schema 不匹配需要明确处理:是阻止工具、附加错误、保留原 output,还是继续。2.1.235 对 PostToolUse 的非法 updated output 会保留原始结果并附 hook error,避免成功工具被坏 hook 破坏。
Stop hook 重入与 cap¶
Stop/SubagentStop blocking 时:
- 保存本轮 assistant output。
- 把 hook feedback 加入 messages。
stopHookActive=true。stopHookBlockingCount + 1。turnCount + 1并重新请求模型。
默认 CLAUDE_CODE_STOP_HOOK_BLOCK_CAP=8。达到用户 maxTurns 或 cap 后,客户端停止继续阻止并发 warning/terminal state。这个熔断保护错误 hook 不把 CLI 永久锁死。
MCP 与动态工具恢复¶
MCP server 断开会让工具 call 失败或从可用表移除。恢复包含两步:
- transport/auth/client 重新连接并重新列 capabilities/tools;
- tool generation 变化触发 Tool Search/schema cache 失效和下一轮工具表刷新。
仅“socket reconnect 成功”不够。如果模型仍看到旧 schema,下一轮会继续产生无效 input。反过来,正在进行的 API request 也不会因 server 刚连上而热注入工具,刷新发生在下一请求边界。
证据见 reverse/javascript/cli.readable.js 491915-491964、231802-231817、272372-272382。
Session resume:恢复逻辑历史,不复活旧进程¶
resume 从 transcript 重建 message graph、compact boundary 和当前 leaf。它能够恢复:
- user/assistant/tool result 文本与结构;
- fork/parent/logical parent;
- compact summary;若该 boundary 来自保留型路径,再恢复 preserved segment;
- 可持久化 session/task metadata;
- file checkpoint 引用仍有效时的 rewind 能力。
它不能自动恢复:
- 已退出进程中的连接、Promise、内存 cache 和 abort controller;
- 普通 shell 子进程;
- 未持久化的 background task;
- 已过期 provider prompt cache;
- MCP server 自己丢失的远端状态。
精确版本 probe 对不存在合法 UUID 返回 No conversation found... 和 exit 1,证明恢复前先要求本地 conversation 存在。完整机制见 会话、检查点与 Memory。
File rewind:有限的补偿恢复¶
文件 checkpoint 可以 dry-run 差异并恢复被跟踪内容,本版最多保留 100 个 checkpoint。它是模型 fallback/用户后悔后的重要补偿,但只覆盖客户端知道的文件写入面。
可靠工具还应提供:
--dry-run或 read-before-write;- idempotency key;
- expected version/ETag;
- 原子写入或临时文件替换;
- 明确的 undo/compensation API;
- 写后 verify。
Agent Loop 的“验证结果”只有在工具返回了真实、可解释的状态时才有意义。
Terminal reason 是恢复合同¶
Agent Loop 结束时需要把“为什么停止”交给 UI/SDK,而不是只有 success/error boolean。本版可观察 reason 包括:
completed;max_turns;background_requested;tool_deferred;hook_stopped;aborted_tools/ 用户取消类状态;- reactive compact、malformed tool、API/model error 的耗尽状态;
- 动态 loop wakeup 等专用结束分支。
SDK 最终结果还会把 max turns 映射成 error_max_turns,并携带 num_turns、usage、cost、stop reason、terminal reason 和 permission denials。调用方可以据此决定展示、恢复、重排队或升级人工处理。
command_lifecycle 与 terminal reason 不是同一层¶
带 UUID 的队列命令还有 queued/started/completed/cancelled/discarded 生命周期。hard error 或 abort 通常映射 cancelled;但 max_turns、hook_stopped、tool_deferred、background_requested 当前映射 completed。这里 completed 只表示“消费它的 turn 受控结束”,不保证内容已回答或 result frame 已送达。
terminal 也可能没有 started,内部命令可能没有 queued;generator throw 甚至可能留下只有 started。exactly-one-terminal 只在单 worker 内成立,CCR 重投后同 UUID 可再次进入生命周期。可靠 SDK wrapper 需要跟踪本进程已见 UUID,并在进程退出时为没有 terminal 的项合成 discarded,而不是把 lifecycle 当严格三段事务日志。
为什么不能只看 exit code¶
交互 CLI、print/SDK、background task 和 tool call 对“成功”的表达不同。某个工具失败后模型修正并最终完成,进程 exit 仍可为 0;达到 maxTurns 则需要 typed terminal reason。可靠自动化应解析结构化 output protocol,而不是只匹配 stderr 文本。
可观测性如何定位恢复层¶
| 现象 | 关键字段/事件 | 判断 |
|---|---|---|
| 首 token 慢 | request start、TTFT、cache read/write | cache miss、provider latency 或请求装配 |
| 中途停顿 | stream stall/watchdog、last event time | 网络/流解析,不一定是工具 |
| 工具前卡住 | PreToolUse duration、permission duration | hook/审批/策略 |
| 工具后不继续 | PostToolBatch、queue absorption、maxTurns | batch hook 或终止条件 |
| 同动作疑似重复 | attempt/model fallback、tool ID、side-effect key | fallback 后模型重发 |
| compact 后质量下降 | pre/post token、messages summarized、attachments;保留型路径再看 preserved fields | summary 信息损失或恢复材料不足 |
| 子 Agent 长时间无结果 | agent API retry、heartbeat、task status | 子循环请求/工具/后台状态 |
| resume 后工具消失 | MCP generation、server status、Tool Search discover | 重连/刷新未完成 |
一个失败链案例¶
用户要求修改配置并运行部署:
- 第一轮模型生成 Edit 和远端 Deploy tool。
- Edit 通过 permission,写入本地文件并建立 checkpoint。
- Deploy MCP tool 已向远端提交,但模型流随后断开。
- 客户端 abort 剩余工具、tombstone provisional assistant,并切 fallback model。
- fallback model 只看到整理后的稳定消息,若没有外部幂等状态,可能再次建议 Deploy。
- file rewind 可以恢复 Edit 前的本地文件,却不能撤销第一次远端部署。
- 正确恢复必须先查询远端 deployment ID/status,再决定继续、补偿或停止;不能盲目重试。
这个案例说明恢复设计的核心不是“让模型再试一次”,而是保持足够的动作身份、结果和外部状态,使下一次决策不会重复不可逆副作用。
运行探针把恢复对象和计数器钉死¶
probe.http-retry-classification 使用相同命令和 CLAUDE_CODE_MAX_RETRIES=2 对比两种状态。受控 529 序列为 529,529,200,总请求数 3、最终 exit 0;受控 400 总请求数 1、exit 1。这里 MAX_RETRIES=2 表示初次请求之外最多再试两次,不能误读成最多两个 HTTP 请求。
probe.model-fallback-sequence 让主模型连续收到三次 529,第四次请求切到 claude-haiku-4-5 并成功。实际模型序列为 primary、primary、primary、fallback。当前官方 Model configuration 也说明 fallback 面向 overloaded/unavailable/qualifying server errors,认证、计费、rate-limit、request-size 和 transport 保持各自处理;该公开主张登记为 public.fallback-chain,本版顺序由 Probe 单独证明。
其他探针把“静态存在恢复分支”推进到以下可观察状态:
- Stop hook 首次阻止后,反馈进入第二个请求;恢复对象是控制流,不撤销已完成动作。
maxTurns=1时工具和 PostToolUse 完成,但不会启动下一次模型调用;终态为error_max_turns、exit 1。- PreToolUse deny 返回 paired error result,模型可以基于拒绝原因继续;工具本体没有执行。
- MCP
tools/list_changed触发重新 list,但新 schema 到第三个请求才可见;恢复有一个请求装配延迟。 - 子 Agent 启动 ACK 与完成通知分离;父循环必须等待 task notification,不能把 ACK 当结果。
- 原有 resume 探针证明 transcript history 可恢复,但所有探针都没有声称回滚远端副作用。
- checkpoint 探针证明本地 Edit 可恢复原字节且 rewind 阶段模型请求为 0,但同一机制不覆盖远端副作用。
这些结果分别对应“重入、预算终止、前置阻断、动态 schema 刷新、异步任务完成、历史恢复”。把它们统一写成“自动重试”会丢失状态保留、重复执行和用户可见结果的差别。
跨版本必须比较什么¶
- retryable status/error 分类、backoff、attempt 上限和 provider 差异;
- stream watchdog/stall、non-stream fallback gate 和 partial salvage;
- model/refusal/server fallback 的 consent、allowlist、tombstone 与 usage 归属;
- 完整 tool block 的执行边界与 abort 行为;
- max_tokens、malformed tool、thinking-only 的 counter 和 exhaustion reason;
- reactive compact 次数、触发错误和消息保留;
- tool error 的结构、
tool_use_id配对和自动/模型重试边界; - hook error fallback、Stop hook cap 与 maxTurns 交互;
- MCP reconnect、generation refresh、schema cache invalidation;
- transcript resume、file checkpoint/rewind 和 background durability;
- terminal reason、SDK subtype、exit status 和 telemetry correlation。
证据位置¶
- Agent Loop 恢复与终止:
reverse/javascript/cli.readable.js271550-272424。 - 无工具恢复分支:
reverse/javascript/cli.readable.js272171-272264。 - Stop hook cap:
reverse/javascript/cli.readable.js272253-272261。 - 工具批次、MCP refresh、queue、maxTurns:
reverse/javascript/cli.readable.js272293-272424。 - 单工具错误映射:
reverse/javascript/cli.readable.js316092-316487。 - resume 修链:
reverse/javascript/cli.readable.js323188-323443。 - file checkpoint/rewind:
reverse/javascript/cli.readable.js194602-194804。 - MCP refresh/cache invalidation:
reverse/javascript/cli.readable.js491915-491964、231802-231817。 - 事件、错误和诊断清单:source inventory。
结构化运行主张:probe.http-retry-classification、probe.model-fallback-sequence、probe.stop-hook-reentry、probe.max-turns-terminal、probe.hook-deny-feedback、probe.mcp-generation-refresh、probe.resume-history、probe.checkpoint-rewind-positive。完整 command/input/literal output/exit status 和请求序列见 精确二进制运行证据指南。
恢复机制的证据边界必须保持清楚:bundle 能证明客户端如何分类、重试、改写消息和发出结果;远端服务是否幂等、服务端内部路由和账户风控仍需要对应 API/服务证据。