Claude Code Agent 循环终止机制:七种 stop_reason 全解析

Claude Code 以响应内容而非 stop_reason 判定循环是否继续,并对可恢复错误设计恢复路径。
本文深入 Claude Code v2.1.220 源码,拆解七种 stop_reason 在 Agent 循环中的真实处理方式。核心发现是:循环是否继续不依赖 stop_reason 标签,而直接检查 assistant 消息内容中是否含 tool_use 块,因为标签与实际结构可能不一致。max_tokens 与上下文溢出被视为可恢复错误,分别触发 token 升级、继续提示与压缩重试,输出恢复最多三次;refusal 是唯一不自动重试的停止原因。为防止恢复路径导致无限循环,系统设置了两道闸门:maxTurns 硬性限制模型调用次数,超出则返回 error_max_turns;Stop hook 拥有最终否决权,可强制再跑一轮。真正完成需同时通过内容、停止原因与 hook 三道检查。
Claude Code 的 Agent 循环在第一篇文章中被简化为「有工具调用就继续,没有就退出」。这个草图虽然直观,却遗漏了模型响应中一个关键字段:stop_reason。它告诉调用方模型为何停止生成——是正常结束、请求工具、触达输出上限、安全拒绝,还是上下文窗口溢出。每一种原因都可能需要不同的处理策略。
本文深入 Claude Code v2.1.220 源码,拆解七种 stop_reason 的真正含义、循环实际依赖的判断信号、保持循环存活的恢复路径,以及防止无限循环的硬性保护。
七种 stop_reason 的定义
Anthropic API 的响应可以包含以下七种停止原因:
| stop_reason | 含义 |
|---|---|
end_turn | 模型认为回答完成,将控制权交还用户 |
tool_use | 模型发出了工具调用请求 |
max_tokens | 服务端因输出 token 上限而截断 |
stop_sequence | 生成内容命中了调用方提供的停止序列 |
refusal | 模型出于安全原因拒绝请求 |
pause_turn | 模型请求暂停当前轮次以便稍后恢复 |
model_context_window_exceeded | 输入超出了模型上下文窗口限制 |
七种取值看起来完备,但 Claude Code 并未对它们一视同仁。

stop_reason 是 Anthropic Messages API 顶层响应字段之一,与 content、role、usage 平级,类型为字符串或 null。在流式(SSE)模式下,它并非随首个事件到达,而是在 message_delta 事件中给出,紧跟 message_stop 之前,因此调用方只有在流基本结束时才能拿到最终值——这也是为什么缓存或逐步读取时需要显式保存该字段。
与之配套的另一个字段是 stop_sequence:当 stop_reason 为 stop_sequence 时,该字段会带回实际命中的那一段字符串,便于调用方判断是哪条停止条件被触发。usage 中的 input_tokens、output_tokens 则用于诊断 max_tokens 与上下文溢出,二者常被一起消费。理解这些字段的到达时机与配套关系,是正确编写循环分支的前提。
反直觉的核心设计:完成与否不看 stop_reason
直觉上,循环退出条件可能写成这样:
if stop_reason == 'end_turn':
break
elif stop_reason == 'tool_use':
execute_tools()
但 Claude Code 并不这样调度。它直接检查 assistant 消息的内容本身:如果 content 数组中包含 tool_use 块,就执行工具;否则退出循环。
原因在于,实践中 stop_reason === 'tool_use' 并不能可靠地反映消息结构。一个响应可能包含真实的工具调用块却报告 end_turn,也可能报告 tool_use 却没有实际内容块。内容才是唯一可信的信号源。
stop_reason 在这里的主要用途是驱动错误提示与恢复交互——比如向用户解释输出被截断或模型拒绝执行——而非作为调度决策的唯一依据。
逐项拆解:每种停止原因的处理路径
end_turn 与 tool_use:正常路径
end_turn 意味着模型正常完成,内容中不含工具调用,循环走完成路径,控制权回到用户。tool_use 则不依赖标签本身,只要内容中有工具调用块,就执行、追加结果、发起下一轮模型调用。
max_tokens:先恢复,再放弃
触达输出上限被视为一种可恢复的错误状态,处理步骤为:
- 先尝试提高
max_tokens限制并重试; - 若升级后仍触顶,追加一条用户消息如
[Output token limit hit, continue],明确要求模型继续被打断的工作; - 最多允许三次恢复尝试(
MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3); - 若恢复仍然失败,将问题暴露给用户。
从外层循环的角度看,这并非「完成」,而是「未完成且系统仍可修复」的工作。
context 溢出:压缩后重试
当上下文过大时,Claude Code 启动响应式压缩路径:缩减历史消息,然后重试。这同样是一个继续分支,而非立即终止。
上下文窗口(context window)指模型单次调用所能接受的最大 token 总数,包含系统提示、历史消息、工具定义与本次输入,是输入与输出的合计上限。一旦超出,服务端无法完成推理,会以 model_context_window_exceeded 拒绝或截断。Claude Code 的响应式压缩(reactive compaction)正是针对这一失败的处理:它通常保留系统提示与最近若干轮对话,将更早的历史总结成摘要或直接丢弃,从而把请求重新压回窗口之内再重试。
压缩与 max_tokens 恢复的区别在于作用对象:max_tokens 触及的是输出侧上限,压缩针对的是输入侧总量。两者都会削减可用信息,因此恢复并非无损,压缩后模型可能丢失早前细节。这也解释了为何需要 maxTurns 之类的硬上限——反复压缩重试在理论上可以无限进行。
refusal:报告后停止
拒绝不同。Claude Code 会构造错误信息——通常建议用户通过 /model 尝试其他模型——但不会自动重试。模型的刻意拒绝不太可能因为重复同一请求而改变,因此循环就此结束。
stop_sequence 与 pause_turn:边界情况
Claude Code 通常不配置自定义停止序列,所以 stop_sequence 几乎总是落到内容检查路径,此时一般没有工具调用,轮次正常完成。
pause_turn 则是一个真实的实现缺口。SDK 能识别这个取值,但 Claude Code 循环中没有专门分支处理它。该值设计用于超长或复杂轮次,而 Claude Code 的主循环通常每个用户请求只包含少量调用,这或许解释了为何尚未需要显式处理。如果未来引入长时推理工作流,这个分支需要明确的策略。
错误即恢复触发器:一种更广泛的设计原则
max_tokens 和上下文溢出的处理揭示了底层设计哲学:只要可恢复,错误就应该触发恢复而非终止。这一原则贯穿多个场景:
max_tokens触发 token 限制升级、显式继续提示与重试;- 上下文溢出触发压缩与重试;
- 过载或速率限制可触发模型降级与重试;
- 网络故障可使用指数退避重试。
目标很明确:修复系统能修复的,只把真正不可恢复的故障暴露给用户。在状态机层面,每一次恢复只是当前循环迭代的不同转移路径。
maxTurns 与 Stop Hook:两道防无限循环闸门
有了上述恢复路径,循环理论上可能永远运行:模型反复请求工具而无进展,输出限制恢复每次升级后再次触顶,压缩后模型立刻再次溢出上下文。Claude Code 为此设置了两道闸门。
第一道是 maxTurns,它对单次用户发起的循环中的模型调用次数计数。一旦超过配置上限,系统强制退出:向消息历史追加 [max_turns_reached],SDK 返回 { subtype: 'error_max_turns' } 结果,用户收到明确的循环因安全限制而停止的提示。Agent 执行可以在没有人工干预的情况下进行多轮迭代,maxTurns 确保缺乏监督不会演变为无限循环。
第二道是 Stop hook。即使循环已看不到工具调用块、本应完成,Stop hook 仍拥有最终否决权。如果它返回 decision: block,循环不得结束,必须再跑一轮。这支持「测试未跑完不得结束」「相关改动未提交不得结束」等策略。
真正的完成需要同时通过三道门:内容中无 tool_use 块;停止原因不要求恢复路径;没有 Stop hook 阻止完成。
在 Claude Code 的语境中,hook 是用户在 Agent 生命周期特定节点挂载的外部脚本或命令,由 SDK 在循环内按约定时机调用,其返回值可影响循环走向。Stop hook 绑定在「模型不再请求工具、即将结束」的时机,接收当前转录并按约定返回 JSON,其中 decision 字段取 block 即表示否决完成,附带的 reason 会以用户消息形式注入下一轮,指导模型继续动作。
与之并列的还有 PreToolUse、PostToolUse、UserPromptSubmit 等 hook 点。Stop hook 的特殊之处在于它位于自动循环的出口,能把「测试是否通过」「改动是否已提交」这类外部可判定的策略注入原本纯由模型驱动的终止条件,相当于一道人工可编程的额外闸门,与内容检查和停止原因检查共同构成完成的必要条件。
完整的轮次结束决策
将所有分支汇总,每次模型调用后的决策流程为:
if content contains a tool_use block:
execute tools, append tool_results, and continue
elif stop_reason == 'max_tokens':
recover output limit (escalate → inject continue → retry)
give up after at most three recoveries
elif stop_reason == 'model_context_window_exceeded':
run reactive compaction, then retry
elif stop_reason == 'refusal':
create an error message and end the loop
elif turn_count > maxTurns:
force an end via the max-turns safeguard
elif Stop hook returns block:
prevent completion and run another iteration
else:
complete the loop
七种 API 取值最终收敛为五种有意义的处理模式。stop_sequence 通常落回内容检查,pause_turn 在当前分析的版本中没有专门的循环层处理。真正的决策依据是内容、异常停止原因、轮次计数与 hook 这四者的组合。
实践启示
对于构建 Agent 系统的开发者,这套机制提供了几点可迁移的经验。完成判定应基于响应内容而非元数据标签,因为标签不可靠是常态而非例外。错误处理应当区分可恢复与不可恢复,前者值得投入恢复逻辑,后者应快速暴露。硬性迭代上限是任何 Agent 循环的必备保险,无论恢复逻辑多么完善。而 hook 机制为人类策略介入提供了在自动循环末尾插入自定义规则的空间。
后续文章将把前四篇讨论的审批、钩子、并行调度、停止原因处理与恢复机制统一到 QueryEngine 主循环状态机的全景视图中。
相关推荐

Workbuddy零基础入门:桌面AI智能体如何落地工作流
Workbuddy桌面AI智能体零基础教程解读:从安装、积分领取到文件管理与自动化工作流搭建,对比CodeX的适用场景与成本差异,帮助职场人完成从AI提问者到AI管理者的思维转变。

OpenAI智能体新栈:计算机操作、决策API与超速工作流
OpenAI Dev Day发布智能体新栈:Computer Use操作速度超越普通人,Decisions API基于Luna实现极低延迟分类,Agents API开放同源计算机操作能力,GPT-6.1 Sol成本仅为Astra七分之一。本文梳理核心更新、实测数据与开发者落地要点。

梁文峰开源推理框架DeepInfo,硬刚美国禁令
DeepSeek在开源模型后再开源推理框架DeepInfo,包含GPU/CPU交叉流水线、小模型预判大模型复核、华为昇腾与英伟达芯片全面适配等技术,直面美国联邦禁令。本文解析其对AI产业落地的意义与开源策略。