你用 Task 工具起了一个 subagent,跑了几分钟、日志也打了、最后顺利结束——结果主 Claude Code 会话要么甩出一个完全通用的总结、要么硬说 subagent「还在跑」、要么直接跳过结果继续往下走。
最快的修法: 让 subagent 这一轮以一条真正的 assistant 文本消息收尾,里面写清楚它动过的文件路径加一段简短总结,并且别原样吐整个文件内容。在你的 Task prompt 里加上这句:「完成后,回复你创建或修改的绝对路径,加 3 条 bullet 的总结。最后一步不要停在工具调用。输出超过约 500 字就写进一个文件、回复路径。」 光这一改就能解决绝大多数回传失败。
原理在架构里。每个 subagent 跑在自己独立的上下文窗口里,只有它最后那条 assistant 消息会回传给 parent——中间的工具调用、日志、文件读取都留在 subagent 内部、直接丢弃(Claude Code 文档:subagents)。如果这条 final message 没了、太长了、或者被原始 transcript 噪音顶替了,parent 就拿不到能用的东西。问题几乎总是出在 subagent 怎么结束这一轮,而不是它有没有做事。
你属于哪一类?
| 主会话里的症状 | 最可能的原因 | 跳到 |
|---|---|---|
| Parent 给个通用「done」、没细节 | Subagent 停在工具调用、没有 final 文本 | 原因 1 |
| Parent 只显示一个开头 / 什么都没有 | Final message 太长、回传时被截断 | 原因 2 |
| 跑完了 parent 还说「还在跑」 | Subagent 崩了或撞 usage limit | 原因 3 |
| 日志里明明有结果、parent 却无视 | Prompt 从没说怎么回报 | 原因 4 |
| Parent 知道「done」、但不知道做了什么 | Subagent 写盘了、没给路径 | 原因 5 |
| Parent 收到一大坨 JSON/JSONL | 后台 agent 的 TaskOutput bug | 原因 6 |
| 结果进了 transcript、下一轮却没用它 | Ctrl-C 打断后的竞态 | 原因 7 |
常见原因
按命中率从高到低。
1. Subagent 最后一步是工具调用、不是 final assistant 消息
如果 subagent 最后一个动作是 Write、Edit 或 Bash 调用、之后再没有一条收尾的 assistant 文本消息,parent 收到的就是空的或残缺结果。Subagent 必须以一段真正的文字回复结尾。
怎么判断: 打开 subagent transcript(见 Step 1),看最后一条记录。如果 type 是 tool_use、或者这行是 toolUseResult 而不是带 text 块的 assistant 消息,bug 就在这。
2. Final message 太长、超过了回传 buffer
Subagent 结果是作为单条消息回传的。极长的输出(大致几千 token 往上)可能被截断,parent 只能看到一个开头甚至什么都看不到。
怎么判断: 看 subagent 最后一条消息长度。如果是在原样吐整个文件而不是总结,多半超长了。
3. Subagent 崩了或撞 usage limit,没产生结果
Subagent token 用完、撞 rate limit、或者抛了内部错误,可能直接退出、根本没产生 final message。Parent 经常把这种「没回」理解成「还在跑」。
怎么判断: 看 subagent 最后几条记录里有没有 error、usage-limit 信号、或者最后那条 assistant turn 上的 max_tokens 停止原因。
4. Parent 没告诉 subagent 怎么回报
Subagent 严格按它收到的指令做事。你说了「做 X」但没说「把结果按 Y 格式回我」,它可能只往 log 打、以为 parent 会自己读——可它读不到。
怎么判断: 重读你写的 Task 描述。有没有写「回复时给我文件路径加一段总结」?没有的话就是这。
5. Subagent 把结果写到磁盘了、但没告诉 parent 路径
很多 subagent 把大量输出存进文件然后用「done」结尾。Parent 完全不知道去哪读,所以在它看来这个结果就是「done」——没用。
怎么判断: 问 parent「subagent 创建了哪个文件?」答不上来,就是 subagent final message 忘了带路径。
6. 后台 agent 回传了原始 transcript、而不是总结
如果你用 run_in_background=true 起了 subagent、再用 TaskOutput 拉它的结果,一个已知的回归 bug 可能把整段对话当成序列化的 JSONL 字符串回传——四十多条消息、几百 KB——而不是 agent 的 final 文本。症状是:parent 上下文暴涨,结果里出现像 {"parentUuid":...,"isSidechain":true,"type":"user",...} 这样的行(anthropics/claude-code #20531)。
怎么判断: 如果回传的结果是带 isSidechain / parentUuid 字段的 JSONL、而不是一段干净的总结,就是撞了这个。后续的 2.1.x 版本已修复——跑 claude --version,再 claude update。更新之前,用 Step 4 的文件 handoff 绕过。
7. Parent 状态老化——subagent 还没回 parent 就往前走了
少数情况(特别是手动 Ctrl-C 之后),parent 在 subagent 回报之前就推进了自己这一轮。结果会落进 transcript、但下一条 assistant 消息根本没消费它。
怎么判断: 看消息顺序。如果在 subagent dispatch 和 subagent return 之间出现了 parent 这边的 assistant 消息,就是撞了这种竞态。
开始前
- 想清楚 subagent 能不能重跑,还是说成本太高重复不起。
- 在 transcript 被覆盖前先存一份(Claude Code 存在
~/.claude/projects/...下)。 - 把你用的 Task prompt 原文记下来,多半要改写。
- 准备一个小复现任务用来测验证修复。
需要收集的信息
- Claude Code 版本:
claude --version(截至 2026 年 6 月,最新是 2.1.x)。 - 你起 subagent 用的完整 Task prompt。
~/.claude/projects/<project>/下的 subagent transcript JSONL。- Subagent 声称跑完之后 parent 的回复。
- Subagent 创建的文件(路径和大小)。
- 会话结束时显示的 token 使用量(如果有)。
一步一步修复
Step 1:找到 subagent transcript
Claude Code 把每个会话写成一个 JSONL 文件,放在按你项目路径命名的目录下。列出最近几个:
ls -lt ~/.claude/projects/*/*.jsonl | head -10
Subagent(sidechain)的运行单独存,一个 agent 一个文件:
ls -lt ~/.claude/projects/*/*/subagents/agent-*.jsonl | head -10
每一行是一条记录;subagent 的行带 "isSidechain": true、靠 parentUuid 串成链。想只挑出某个 session 文件里 subagent 的轮次:
grep '"isSidechain":true' ~/.claude/projects/<project>/<session-id>.jsonl | tail -5
最后一行会告诉你 subagent 到底是怎么结束的。
Step 2:确认 final message 存在
健康的 subagent 以一条内容里带 text 块的 assistant 记录结尾,比如:
{ "type": "assistant", "message": { "content": [ { "type": "text", "text": "Done. Wrote /tmp/report.md ..." } ] } }
如果最后一条是 toolUseResult(或者一条内容只有 tool_use 的 assistant 记录)、后面再没接 assistant 文本,subagent 就是没产出 final 回复。这是原因 1。
Step 3:改写 Task prompt、明确要结构化回复
不要写「调查 X」,写成:
调查 X。完成后回复时严格包含:
- 你创建或修改的文件路径(绝对路径)
- 3 条 bullet 的发现总结
- 任何遗留问题
最后一步不要停在工具调用。
明确格式要求能显著降低回传失败率。如果你反复要起同一种 worker,就把这套约定固化成一个可复用的 subagent 定义,放在 .claude/agents/ 下(用 /agents 创建),这样每次运行都继承同样的回报规则。
Step 4:把 subagent 输出长度封住(文件 handoff)
让 subagent 总结而不是原样吐:
如果结果超过约 500 字,把完整输出写到一个文件、
回复时只给绝对路径加一段简短总结。
这能避开原因 2 的 relay buffer 截断,同时也是原因 6 里 TaskOutput 后台 agent bug 的绕法:parent 直接读文件,不去消费那个臃肿的 task.output 字段。
Step 5:用小任务复现
用同一个 subagent 跑一个琐碎任务(「列出 src/ 下 3 个文件」)。小任务能正常回传,那原来的失败就是大小或格式问题。小任务也失败,怀疑崩溃或 usage limit。
Step 6:查 usage-limit 或 token-cap 报错
在 subagent transcript 里搜 usage_limit、max_tokens、error:
grep -E 'usage_limit|max_tokens|"type":"error"' ~/.claude/projects/<project>/<session-id>.jsonl
找到的话,缩窄 subagent 的范围、或者拆成两个 subagent。limit 这一侧详见 Claude Code 到一半因 usage limit 停下。
Step 7:竞态情况下,重跑时别用 Ctrl-C 打断
Subagent 在跑的时候别按 Ctrl-C,让它自己跑完。一定要取消,就整轮取消重起,不要半途打断。
怎么验证修好了
- Parent 下一条 assistant 消息引用了 subagent 真实的发现,不是通用的「done」。
- Subagent 写的文件在 parent 回复里有具体绝对路径。
- 同一个 prompt 重跑,结果回传稳定。
- Subagent transcript 最后一条记录是一条
assistant的text消息,不是工具调用、也不是一坨原始 JSONL。
长期预防
- 写 Task prompt 时用一段固定模板:「按结构化总结回复,最后一步不要停在工具调用」。
- 任何输出超过几百行的 subagent 都走文件 handoff。
- 给 subagent 任务范围硬上限:一个 subagent 一个明确小任务。
- 定期翻 transcript,找反复出现的回传失败、迭代你的 prompt 模板或
.claude/agents/定义。 - 范围一大就用多个小 subagent,不要一个长 subagent 撑下来。
- 用
claude update把 Claude Code 保持最新;2.1.x 这一路修了好几个回传和TaskOutput的回归 bug。
容易踩的坑
- 以为 parent 能「看到」subagent 中间的日志。看不到,只有 final message 会回。
- 让 subagent 把整个文件内容原样吐回来、不做总结。
- Ctrl-C 半路打断、然后指望 parent 自动恢复优雅状态。
- 忘了 subagent 跑在隔离上下文里,除非明说它读不到 parent 的变量或文件。
- 给一个根本不需要隔离的任务也起 subagent,有时候 inline 更合适。
常见问答
- 为什么 subagent 跑完了 parent 还说在跑? Final message 缺失或为空(原因 1 或 3)。Parent 没收到完成信号,要么等、要么乱猜。
- Parent 能读到 subagent 的工具调用吗? 读不到。只有收尾的 assistant 文本消息会回传,中间的工具调用和结果都丢掉。
- 回传消息有大小上限吗? 没有公开的硬数字,但实操上你要让 final message 控制在几千 token 这个量级。再多就总结、或写盘后回传路径。
- 为什么我收到一大坨 JSONL 而不是总结? 那是
TaskOutput后台 agent 的回归 bug(原因 6)。用claude update更新 Claude Code,同时让 agent 写文件、回传路径。 - 插件定义的 agent 和自定义 agent 也会这样吗? 会。所有通过 Task 起的 subagent,包括内置的 Explore、Plan,以及
.claude/agents/里的,都遵循同一套 final-message 回传契约。 - subagent transcript 存在哪? 在
~/.claude/projects/<project>/下,是.jsonl。Subagent 的运行在<session-id>/subagents/agent-*.jsonl;subagent 的行带"isSidechain": true。