v2: Anthropic Messages transform throws on round-trip of provider-executed tool results it doesn't recognize, instead of degrading · Issue #53633 · anomalyco/opencode · GitHub
Skip to content

v2: Anthropic Messages transform throws on round-trip of provider-executed tool results it doesn't recognize, instead of degrading #53633

Description

@nic-lan

Description

Summary

packages/llm/src/protocols/anthropic-messages.ts refuses to lower any assistant history containing a provider-executed (providerExecuted: true) tool-result whose tool name is not one of Anthropic's three native server tools (web_search, code_execution, web_fetch). Instead of degrading the unrecognized result, it fails the whole request with:

"Anthropic Messages does not know how to round-trip server tool result for "

This happens before any network call, during message lowering. The practical effect: once a conversation's history contains a provider-executed tool result that isn't one of those three (for example a provider-executed read), that conversation can no longer be continued on an Anthropic-API provider (e.g. Anthropic direct, Bedrock, Vertex). Switching provider mid-conversation — or otherwise replaying that history to an Anthropic route — is permanently blocked for that session.

Root cause

packages/llm/src/protocols/anthropic-messages.ts

The name mapper only knows three server tools (lines ~293-298):

const serverToolResultType = (name: string): AnthropicServerToolResultType | undefined => {
  if (name === "web_search") return "web_search_tool_result"
  if (name === "code_execution") return "code_execution_tool_result"
  if (name === "web_fetch") return "web_fetch_tool_result"
  return undefined
}

lowerServerToolResult throws on anything else (lines ~300-305):

const lowerServerToolResult = Effect.fn("AnthropicMessages.lowerServerToolResult")(function* (part: ToolResultPart) {
  const wireType = serverToolResultType(part.name)
  if (!wireType)
    return yield* invalid(`Anthropic Messages does not know how to round-trip server tool result for ${part.name}`)
  return { type: wireType, tool_use_id: part.id, content: part.result.value } satisfies AnthropicServerToolResultBlock
})

And it is reached for every provider-executed tool-result during lowering (lines ~461-463):

if (part.type === "tool-result" && part.providerExecuted) {
  content.push(yield* lowerServerToolResult(part))
  continue
}

So any providerExecuted: true tool-result with a name outside those three hard-fails the request.

Precedent

This is the Anthropic Messages analog of #29159 ("fix(llm): replay OpenAI Responses hosted tool history as native input items"), which was the same defect on the OpenAI Responses adapter — assistant history containing provider-executed hosted tool results was rejected during lowering with "only support text, reasoning, and tool-call content". That was fixed by teaching lowerMessages to lower the provider-executed pairs instead of rejecting them. The Anthropic Messages transform has the equivalent gap and never received the equivalent treatment.

Related: #45261 also concerns provider-executed tool calls breaking the rest of an Anthropic session (via repairToolCall), i.e. a nearby class of "provider-executed content is mishandled on the Anthropic path".

Suggested direction (not prescriptive)

Mirror #29159: in the Anthropic Messages transform, handle an unrecognized provider-executed tool-result by degrading it rather than throwing — e.g. lowering it to a normal content block (text, or a best-effort representation of the structured result) so the history remains replayable.
One caveat worth the maintainers' judgement: a native Anthropic tool_result block normally pairs with a client tool_use, which a provider-executed call does not have, so the exact safe representation (plain text vs. a synthesized block) needs deciding. The key ask is simply that an unrecognized provider-executed result should not hard-fail the entire request — a conversation should remain portable to an Anthropic provider.

Plugins

No response

OpenCode version

v2.0.18.

Steps to reproduce

  1. Have a conversation whose assistant history contains a tool-result with providerExecuted: true and a tool name that is not web_search / code_execution / web_fetch (any provider/runtime that surfaces a locally/externally executed tool as provider-executed will produce one — e.g. a provider-executed read).
  2. Continue that conversation on an Anthropic-API route (Anthropic, Bedrock, or Vertex Anthropic).
  3. The request fails during lowering with Anthropic Messages does not know how to round-trip server tool result for — no network call is made.

Screenshot and/or share link

No response

Operating System

No response

Terminal

No response

Activity

  1. opencode-agent commented on Oct 7, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I can confirm this on v2.0.18, and the current v2 branch behaves the same way.

    I tested it at the protocol level: I built a request for the Anthropic Messages route whose assistant history holds a providerExecuted: true tool-call/tool-result pair, then compiled it.

    • With web_search the request compiles normally.
    • With other names (read, file_search, tool_search_tool_regex) it fails during lowering, before any network call, with Anthropic Messages does not know how to round-trip server tool result for <name>.

    I couldn't test a real provider switch end to end here, so if you can share which provider/model produced the provider-executed result in your session, that would help pin down the realistic trigger.

  2. nic-lan commented on Oct 8, 2026

    @nic-lan
    Author

    The realistic trigger is a mid-session provider switch — starting on claude-code/claude-opus-5 (which routes through the Claude Code CLI) and switching to google-vertex/claude-opus-4-6@default (Vertex AI, Anthropic Messages protocol) within the same conversation.

    The Claude Code CLI executes tools natively (e.g. read, bash, edit), and those results end up in the conversation history as providerExecuted: true. When the conversation then continues on the Vertex provider, the Anthropic Messages transform fails on those tool results during lowering.

    The switch can happen for any reason — manual model change, or automated (e.g. rate-limit-triggered). The effect is that any conversation that has used Claude Code's native tools becomes permanently non-portable to an Anthropic-API provider.

  3. nic-lan commented on Oct 19, 2026

    @nic-lan
    Author

    Still reproducible on dev — anthropic-messages.ts:608 throws does not know how to round-trip server tool result for any provider-executed tool outside web_search / code_execution / web_fetch.

    PR with a fix and regression tests: #55560

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions