Skip to content

Foreign server_tool_use id from a built-in-tool provider poisons the Claude history (400 srvtoolu_) #3685

Description

@f-liva

Summary

Anthropic validates server_tool_use.id against ^srvtoolu_[a-zA-Z0-9_]+$ and rejects the entire request with a 400 when one does not match.

A provider that runs its own built-in tools emits its native id instead of an Anthropic one. z.ai/glm does this for analyze_image, producing OpenAI-style call_ ids:

{"type": "server_tool_use", "id": "call_50b82aba1b754d82a4408a53", "name": "analyze_image", "input": {}}

In a mixed combo those blocks stay in the conversation history. As soon as the combo routes a later turn to a Claude provider, the passthrough body carries the foreign id and the API rejects it:

API Error: 400 messages.1.content.1.server_tool_use.id:
String should match pattern '^srvtoolu_[a-zA-Z0-9_]+$'

The session cannot recover on its own: the bad block is now part of the history, so every subsequent turn fails the same way.

Reproduction

  1. Set up a combo mixing a Claude provider and glm/glm-5.3.
  2. Let a turn land on GLM and trigger its built-in analyze_image tool, so a server_tool_use block with a call_ id enters the history.
  3. Let a later turn route to the Claude provider → 400 on every attempt from then on.

Expected

The same treatment normalizeClaudePassthrough already applies to foreign thinking signatures — claude.js#L164-L166 notes that a combo mixing models leaks foreign signatures into history and drops them. A foreign server_tool_use id is the same class of leak and should be dropped before the request reaches Anthropic, along with the tool_result that references it (an orphan result is rejected in turn).

PR follows.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions