-
Notifications
You must be signed in to change notification settings - Fork 457
Description
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 NoneWhen 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.")
returnThe 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)