Skip to content

ServerToolResult not delivered when client tools interrupt the stream (code_execution + client tool concurrency) #1325

@agorski-comp

Description

@agorski-comp

Bug Report: ServerToolResult not delivered when client tools interrupt the stream

Summary

When the Messages API returns stop_reason=tool_use (requesting execution of a client-defined tool) while server-side tools (code_execution, web_search) are running concurrently in the same turn, the ServerToolResult content blocks for those server tools are never delivered to the caller — neither during streaming, in the final message, nor in the subsequent API call.

Reproduction

Setup

  • Model: claude-sonnet-4-6 (also observed on claude-opus-4-6)
  • Tools: code_execution_20250825 (server-side) + a client-defined tool (e.g. ask_internal_tool)
  • Streaming mode via Python SDK client.messages.stream()

Steps

  1. Send a message that triggers Claude to invoke both a server tool and a client tool in the same turn
  2. Example prompt: "Search our database for port activity data and create an Excel spreadsheet with a benchmarking report" (where the database search is a client-side tool)

What happens

  1. Stream begins
  2. content_block_start with server_tool_use (e.g. text_editor_code_execution, id=srvtoolu_A)
  3. content_block_start with server_tool_use (e.g. web_search, id=srvtoolu_B)
  4. content_block_start with tool_use (client tool, id=toolu_C)
  5. message_delta with stop_reason=tool_use
  6. message_stop

Missing: No content_block_start with text_editor_code_execution_tool_result for srvtoolu_A, no web_search_tool_result for srvtoolu_B. The stream ends before these arrive.

What happens in the next API call

  1. We send the client tool result back in the conversation
  2. Claude continues and produces the file successfully using new tool calls
  3. The original srvtoolu_A and srvtoolu_B results are never delivered as content blocks — neither in the streaming events nor in get_final_message()

Expected behavior

Either:

  • (a) The stream should wait for all pending server tools to complete before returning stop_reason=tool_use, OR
  • (b) The ServerToolResult blocks should be included in the response of the next API call (since the results were computed and are available in the container), OR
  • (c) The get_final_message() from the first call should include the ServerToolResult blocks for tools that completed while the stream was open

Impact

Any application that uses server-side tools (code_execution, web_search) alongside client-defined tools will have orphaned server_tool_use blocks with no matching result. This causes:

  1. Frontend stuck spinners — tool progress UI shows a tool as "running" forever
  2. Incorrect error detection — monitoring/load tests flag these as failures even though the operation succeeded
  3. Workaround burden — applications must implement heuristic cleanup (assume completion at next iteration) rather than relying on the API contract

Evidence

From our production load test (5 concurrent users, file-generation questions):

Test Orphaned tools File produced? stop_reason
user_2 q1 text_editor_code_execution Yes end_turn
user_3 q1 text_editor_code_execution Yes end_turn
user_4 q1 text_editor_code_execution + web_search Yes end_turn
user_5 q1 text_editor_code_execution + web_search Yes end_turn

All cases: server tool started at t=4-6s, client tool started at t=7-14s, stream ended for client tool. Server tool result never delivered. File was eventually produced in subsequent iterations.

Raw event trace (user_4, showing both orphaned types)

t=4.1s   content_block_start  server_tool_use  text_editor_code_execution  (srvtoolu_A)
t=4.7s   content_block_start  server_tool_use  web_search                  (srvtoolu_B)
t=7.9s   content_block_start  tool_use         ask_internal_tool             (toolu_C)
         ... no content_block_start for *_tool_result for srvtoolu_A or srvtoolu_B ...
         message_delta  stop_reason=tool_use
         message_stop

--- Next API call (with toolu_C result) ---

t=319s   content_block_start  server_tool_use  text_editor_code_execution  (srvtoolu_D)  <- NEW tool
         ... srvtoolu_A and srvtoolu_B results never appear ...

Environment

  • anthropic Python SDK version: 0.52.0
  • Model: claude-sonnet-4-6
  • Tools: code_execution_20250825 + custom client tools
  • Streaming: yes
  • Container: reused across iterations via container.id

Workaround

We emit synthetic tool_result events for orphaned server tools at the start of the next agentic loop iteration, assuming they completed successfully (since Anthropic continues the conversation without error).

Metadata

Metadata

Assignees

No one assigned

    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