Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Anthropic 400 `all content must be type text if is_error is true` after a parallel `handoff` + tool call (suspected: `tool_reference` content combined with `is_error`)

クローズ
#4,671 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@aheritier がすでに取り組んでいます。

2026年10月21日 から。

  • #4695 @aheritier による — マージ済み

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
48/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
go

調査の方向性

Read pkg/model/provider/anthropic/beta_converter.go, especially convertBetaToolResultBlockWithDeferred, and trace how pkg/runtime/loop.go:918 sets the deferral load point. First add a regression test for a parallel tool batch where the deferred result has IsError set; the issue reports this scenario as suspected and unverified. Done means confirming the failing conversion path and ensuring the resulting request is accepted by the Anthropic API.

索引モデルが issue の本文から書いたものです。

説明

area/providers/anthropic area/runtime area/tools

Version: docker-agent v1.151.0 (the converter code on main is identical), model anthropic/claude-opus-5-5, Beta API with adaptive thinking.

Summary

A session died with a non-retryable 400 on the first model call after a handoff:

error receiving from stream: HTTP 400: invalid_request_error:
messages.66.content.1.tool_result: all content must be type `text` if `is_error` is true

Impact

The whole session becomes unusable. The bad tool_result is part of the conversation history, so every later model call resends it and fails the same way. There is no way to continue or recover in place.

All the work in that session is lost: the context built up over a long multi-agent run, the intermediate results, and the tokens spent producing them. In our case the session was about 100 messages deep, with many tool calls and a long transfer_task result. The failure comes from an unrelated tool that happened to be last in the batch, not from anything the user or agent did wrong, and it gives no warning beforehand. The only remedy is to start a new session from scratch.

Confidence: the log is not conclusive

The debug log does not prove this diagnosis. The log records tool names and output lengths, but not message bodies or is_error flags. What I know and what I'm inferring:

Confirmed from the log

  • root issued two parallel tool calls in one turn: handoff (to planner, 673-char output) and update_todos (50-char output).
  • planner's first request had 67 messages and was rejected at messages.66.content.1, which is the second result in the last user message. By ordering, that is the update_todos result.
  • The same session earlier had a successful handoff + create_todos pair (2919-char output).
  • This is the only occurrence in the retained logs, which cover about one day.

Inferred, not confirmed

  • That the update_todos result had IsError == true. I never saw its content. The 50-char length and the 400 are consistent with a short error such as an unknown todo id, but a short success message would also be that length.
  • That the failing block is a tool_reference result. I'm inferring this from the code path below and from the error text. The log does not show which branch of the converter ran.
  • That the update_todos call id was the deferral load point. It is only the expected value given lastToolCallID(messages).

Not done

  • I have not reproduced this in isolation or in a test.

If a maintainer can confirm which branch of convertBetaToolResultBlockWithDeferred produced content.1, or has a debug flag that dumps the outgoing request, that would settle it.

Suspected root cause (from reading the code)

  1. planner exposes tools that weren't in the session's initial tool set. DeferralTracker.MarkAt (pkg/tools/deferrals.go) marks them Deferred, with DeferredAtToolCallID set to the id passed in at pkg/runtime/loop.go:918: r.toolDeferrals.MarkAt(sess.ID, lastToolCallID(messages), agentTools). That id is the last tool message in the history, which here would be the update_todos call that ran alongside the handoff.
  2. In convertBetaToolResultBlockWithDeferred (pkg/model/provider/anthropic/beta_converter.go), a result whose call id has deferred names becomes a tool_result whose content is tool_reference blocks, with IsError copied from the message:
    return anthropic.BetaContentBlockParamUnion{OfToolResult: &anthropic.BetaToolResultBlockParam{
        ToolUseID: msg.ToolCallID,
        IsError:   param.NewOpt(msg.IsError),
        Content:   content, // tool_reference blocks
    }}, nil
    
  3. If that call's result is an error, the request has is_error: true with non-text content, which the API rejects with exactly this message.

If the diagnosis is right, whether a session dies depends on whether an unrelated tool that happened to be last in the batch succeeded.

Suggested fix (if the diagnosis holds)

Any of these would work:

  • In convertBetaToolResultBlockWithDeferred, skip the tool_reference branch when msg.IsError is true and emit the normal text result.
  • In the runtime, attach the load point to the last non-error tool call, or to the handoff call itself, rather than blindly to lastToolCallID.
  • Add a regression test for a parallel tool batch where the last result has IsError: true and DeferredAtToolCallID points at it.

Because the failure is unrecoverable, a defensive sanitize step before sending would also help. It would downgrade any is_error result with non-text content to a plain text result instead of failing the whole session.

Suspected repro (untested)

  1. Multi-agent config where root has a handoff to planner, and planner has tools that root doesn't, so they're deferred after the handoff.
  2. Have root emit handoff and a tool that returns an error (for example update_todos with an unknown id) in the same assistant turn, with the failing tool last.
  3. planner's first request should return the 400.
主要言語
Go
スター
4.6k
フォーク
522
平均マージ
10時間 33分
マージ済み PR(30日)
231

環境構築

Codespaces で開く

このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。

  • Dockerfile または Docker Compose ファイルあり
  • プルリクエストのテンプレートなし
  • コントリビューションガイドなし

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

docker/docker-agent のほかの issue

docker/docker-agent の issue をすべて見る

似ている issue

Go の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。