Skip to content

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

@carlymr

Description

@carlymr

Description

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

Root cause

In _beta_runner.py, the __run__ loop calls generate_tool_call_response() after each iteration. That method only looks for blocks with type == "tool_use":

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

When a response contains only server_tool_use blocks (which have type == "server_tool_use"), this returns None, causing the loop to exit:

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

The runner should instead recognize that pause_turn means the server is still processing and continue the loop without requiring a client-side tool result.

Reproduction

import anthropic

client = anthropic.Anthropic()

@anthropic.beta_tool
def save_recipe(title: str, ingredients: list[str], steps: list[str], servings: float):
    with open(title + ".txt", "w") as f:
        f.write(title + "\n")

runner = client.beta.messages.tool_runner(
    model="claude-sonnet-4-5-20250929",
    max_tokens=1024,
    betas=["web-fetch-2025-09-10"],
    tools=[
        save_recipe,
        {"type": "web_search_20250305", "name": "web_search", "max_uses": 5},
        {"type": "web_fetch_20250910", "name": "web_fetch", "max_uses": 5},
    ],
    messages=[{"role": "user", "content": "Search for a brownie recipe and save it"}],
)

final = runner.until_done()
# final.content[0] is a BetaServerToolUseBlock, not text —
# the runner stopped before the server tool was resolved
print(final.content[0].type)  # "server_tool_use"

Expected behavior

The tool runner should continue looping when it receives a pause_turn stop reason with server tool use blocks, sending the response back as a continuation until the server tools are resolved and Claude produces a final text response.

Actual behavior

The runner exits the loop and until_done() returns a message whose content is a BetaServerToolUseBlock, causing an AttributeError if you access .content[0].text.

SDK version

0.79.0 (latest as of 2025-02-07)

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