tool_use_failed
Claude returned a tool_use block, but your code sent back a malformed tool_result. The fix is almost always a schema mismatch — wrong content structure, missing tool_use_id, or wrong message role.
The complete tool use flow
Understanding the full cycle prevents most errors:
# Step 1: Send a request with tools defined
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=[{
"name": "get_weather",
"description": "Get weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"}
},
"required": ["city"]
}
}],
messages=[{"role": "user", "content": "What's the weather in Paris?"}]
)
# Step 2: Check if Claude wants to use a tool
if response.stop_reason == "tool_use":
tool_use_block = next(b for b in response.content if b.type == "tool_use")
tool_name = tool_use_block.name # "get_weather"
tool_input = tool_use_block.input # {"city": "Paris"}
tool_use_id = tool_use_block.id # "toolu_01XYZ..."
# Step 3: Run the actual tool
result = get_weather(tool_input["city"]) # your function
# Step 4: Send result back (CRITICAL: correct format!)
final_response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=[...], # same tools
messages=[
{"role": "user", "content": "What's the weather in Paris?"},
{"role": "assistant", "content": response.content}, # full content list!
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_use_id, # must match!
"content": str(result) # string or content array
}]
}
]
)
Correct tool_result schema
# Minimal correct tool_result (string content)
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01XYZ...", # from Claude's tool_use block
"content": "The weather in Paris is 22°C and sunny."
}
]
}
# With structured content (for multiple blocks)
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01XYZ...",
"content": [
{"type": "text", "text": "Result: 42"},
{"type": "text", "text": "Additional info here"}
]
}
]
}
# Reporting a tool error to Claude
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toulu_01XYZ...",
"is_error": true,
"content": "Error: city 'Xyz' not found in weather database"
}
]
}
Common mistakes
1. Putting tool_result in assistant role (wrong!)
# WRONG — tool_result must be in a user message
{"role": "assistant", "content": [{"type": "tool_result", ...}]}
# CORRECT
{"role": "user", "content": [{"type": "tool_result", ...}]}
2. Using string content instead of list
# WRONG — content must be a list when it contains a tool_result
{"role": "user", "content": "The temperature is 22C"}
# CORRECT
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": "...", "content": "22C"}]}
3. Sending only the tool_result turn (missing history)
# WRONG — Claude's tool_use turn must be in messages history
messages=[
{"role": "user", "content": "What's the weather?"},
# missing: assistant's response with tool_use block!
{"role": "user", "content": [{"type": "tool_result", ...}]}
]
# CORRECT — include the assistant's full response.content
messages=[
{"role": "user", "content": "What's the weather?"},
{"role": "assistant", "content": response.content}, # the full content list
{"role": "user", "content": [{"type": "tool_result", ...}]}
]
4. Mismatched tool_use_id
# Claude returns: {"type": "tool_use", "id": "toolu_01AbC...", ...}
# You must echo THAT exact id back:
{"type": "tool_result", "tool_use_id": "toolu_01AbC...", ...} # exact match
TypeScript implementation
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function runToolLoop(userMessage: string) {
const tools: Anthropic.Tool[] = [{
name: "get_weather",
description: "Get current weather for a city",
input_schema: {
type: "object" as const,
properties: { city: { type: "string" } },
required: ["city"],
},
}];
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: userMessage }
];
while (true) {
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
tools,
messages,
});
// Add assistant's full response to history
messages.push({ role: "assistant", content: response.content });
if (response.stop_reason !== "tool_use") break;
// Collect all tool results
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type === "tool_use") {
const result = await dispatchTool(block.name, block.input as any);
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: String(result),
});
}
}
messages.push({ role: "user", content: toolResults });
}
return response;
}
FAQ
Can I have multiple tool uses in one response?
Yes — Claude may call multiple tools in a single response. You must return a
tool_result for every tool_use block before Claude can continue. Collect all results in one user message.What happens if my tool throws an exception?
Return the error to Claude using
"is_error": true in the tool_result. Claude will acknowledge the failure and may suggest alternatives. Never crash your agentic loop on a tool error.Can tool_result content contain images?
Yes — use the content array form and include image blocks:
[{"type": "image", "source": {...}}, {"type": "text", "text": "description"}].