跳转至

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 字节 当前逻辑分支或文件版本 旧进程、数据库、远端事务

Claude Code 按失败对象选择请求重试、状态重建或持久恢复,并在结束前重新验证真实状态

后文按这五类对象展开,并用 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。收到被扣住的错误后,它按下面顺序决定:

  1. Rapid-refill breaker:若已经连续 3 次在上次 compact 后不足 3 个 turn 又触顶,直接 surface thrashing 文案,避免继续烧 summary 请求。
  2. 能否压缩:合法 message group 少于 2 时,压缩没有意义;客户端还会比较 conversation estimate 是否占请求至少 80%,告诉用户主要是历史本身,还是 system/tools/attachments。
  3. 恢复资格:auto-compact 未禁用、不是 compact 自己、不是被排除的辅助 query(已有可复用 precomputed swap 是例外)、远端 gate允许、未 abort、此前未尝试。
  4. 先 precomputed,后 reactive:检查准备好的 swap;否则运行 PreCompact Hook并发起一次 reactive summary。
  5. 成功:outward stream只发boundary、summary、attachments/hooks;preserved messages留在重建后的active history,不向外重复发一遍。随后transition=precomputed_compact_swap或reactive_compact_retry,同一个逻辑turn重新请求。
  6. 失败:除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 时:

  1. 保存本轮 assistant output。
  2. 把 hook feedback 加入 messages。
  3. stopHookActive=true。
  4. stopHookBlockingCount + 1。
  5. turnCount + 1 并重新请求模型。

默认 CLAUDE_CODE_STOP_HOOK_BLOCK_CAP=8。达到用户 maxTurns 或 cap 后,客户端停止继续阻止并发 warning/terminal state。这个熔断保护错误 hook 不把 CLI 永久锁死。

MCP 与动态工具恢复

MCP server 断开会让工具 call 失败或从可用表移除。恢复包含两步:

  1. transport/auth/client 重新连接并重新列 capabilities/tools;
  2. 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 重连/刷新未完成

一个失败链案例

用户要求修改配置并运行部署:

  1. 第一轮模型生成 Edit 和远端 Deploy tool。
  2. Edit 通过 permission,写入本地文件并建立 checkpoint。
  3. Deploy MCP tool 已向远端提交,但模型流随后断开。
  4. 客户端 abort 剩余工具、tombstone provisional assistant,并切 fallback model。
  5. fallback model 只看到整理后的稳定消息,若没有外部幂等状态,可能再次建议 Deploy。
  6. file rewind 可以恢复 Edit 前的本地文件,却不能撤销第一次远端部署。
  7. 正确恢复必须先查询远端 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.js 271550-272424。
  • 无工具恢复分支:reverse/javascript/cli.readable.js 272171-272264。
  • Stop hook cap:reverse/javascript/cli.readable.js 272253-272261。
  • 工具批次、MCP refresh、queue、maxTurns:reverse/javascript/cli.readable.js 272293-272424。
  • 单工具错误映射:reverse/javascript/cli.readable.js 316092-316487。
  • resume 修链:reverse/javascript/cli.readable.js 323188-323443。
  • file checkpoint/rewind:reverse/javascript/cli.readable.js 194602-194804。
  • MCP refresh/cache invalidation:reverse/javascript/cli.readable.js 491915-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/服务证据。