Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de 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`)

Cerrado
#4,671 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

@aheritier ya está trabajando en esto.

Desde el 21/10/2026.

  • #4695 de @aheritier — fusionado

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
48/100
Tipo de issue
Error
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
go

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.
Lenguaje dominante
Go
Estrellas
4.6k
Forks
522
Merge medio
8 h 43 min
PR fusionados (30 d)
239

Preparar el entorno

Abrir en Codespaces

Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.

  • Incluye un Dockerfile o un archivo de Docker Compose
  • Sin plantilla de pull request
  • Sin guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de docker/docker-agent

Todos los issues de docker/docker-agent

Issues similares

Más issues de Go

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.