Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

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`)

Đã đóng
#4,671 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@aheritier đang làm issue này rồi.

Từ ngày 21/10/2026.

  • #4695 của @aheritier — đã merge

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
48/100
Loại issue
Lỗi
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
go
Lĩnh vực
backend-api-design

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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.
Ngôn ngữ chính
Go
Star
4.6k
Fork
522
Merge trung bình
8 giờ 43 phút
Pull request đã merge (30 ngày)
239

Chuẩn bị môi trường

Mở trong Codespaces

Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.

  • Có Dockerfile hoặc tệp Docker Compose
  • Không có mẫu pull request
  • Không có hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của docker/docker-agent

Tất cả issue của docker/docker-agent

Issue tương tự

Thêm issue về Go

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.