Skip to content

fix(tools): continue tool_runner loop on pause_turn with server_tool_use blocks#1235

Open
s-zx wants to merge 1 commit intoanthropics:mainfrom
s-zx:fix/1170-tool-runner-pause-turn
Open

fix(tools): continue tool_runner loop on pause_turn with server_tool_use blocks#1235
s-zx wants to merge 1 commit intoanthropics:mainfrom
s-zx:fix/1170-tool-runner-pause-turn

Conversation

@s-zx
Copy link

@s-zx s-zx commented Mar 8, 2026

Problem

When using client.beta.messages.tool_runner() with server-side tools (web_search, web_fetch) alongside @beta_tool client functions, the runner exits prematurely when the API responds with stop_reason="pause_turn" and server_tool_use blocks.

until_done() then returns a message whose last content block is a BetaServerToolUseBlock instead of text, causing AttributeError: 'BetaServerToolUseBlock' object has no attribute 'text'.

Closes #1170

Root Cause

_generate_tool_call_response() filters content for type == "tool_use" blocks. When the response contains only server_tool_use blocks, this filter returns nothing and the method returns None:

tool_use_blocks = [block for block in content if block.type == "tool_use"]
if not tool_use_blocks:
    return None   # ← exits here

The __run__ loop then sees None and exits:

response = self.generate_tool_call_response()
if response is None:
    log.debug("Tool call was not requested, exiting from tool runner loop.")
    return   # ← premature exit

Fix

Add _has_pause_turn_server_tools() to both BaseSyncToolRunner and BaseAsyncToolRunner. In __run__ (sync and async), check this helper before deciding to exit. When it returns True:

  • Skip generate_tool_call_response() entirely
  • Continue the loop without appending a new user turn — the server will resume on the next API call
if self._has_pause_turn_server_tools():
    log.debug("Received pause_turn with server_tool_use blocks; continuing...")
else:
    response = self.generate_tool_call_response()
    if response is None:
        return   # only exit when there are genuinely no tools

…use blocks

When using client.beta.messages.tool_runner() with server-side tools
(web_search, web_fetch) alongside @beta_tool functions, the runner
exited prematurely when the API responded with stop_reason='pause_turn'
and server_tool_use blocks.

Root cause: _generate_tool_call_response() filters content for blocks
with type='tool_use'. When only server_tool_use blocks are present, the
filter returns an empty list and the method returns None. The __run__
loop then exits:

  response = self.generate_tool_call_response()
  if response is None:
      return   <- exits even though server is still processing

Fix: add _has_pause_turn_server_tools() helper to both
BaseSyncToolRunner and BaseAsyncToolRunner. In __run__ (sync and async),
check this helper before deciding to exit. When True, skip the
generate_tool_call_response call and continue the loop without appending
a new user turn — the server will resume on the next API call.

Fixes anthropics#1170
@s-zx s-zx requested a review from a team as a code owner March 8, 2026 22:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tool runner exits early when response contains only server tool use blocks (e.g. web_search, web_fetch) and stop reason: pause_turn

1 participant