Error database

BadRequestError 400: tool_use ids were found without tool_result blocks (Anthropic)

After the model calls a tool, your very next message must return a tool_result for every tool_use id. Rebuild the conversation so the pairs match exactly.

The message you saw
BadRequestError 400: tool_use ids were found without tool_result blocks (Anthropic)

By Updated

The error

Output
anthropic.BadRequestError: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'messages.1: `tool_use` ids were found without `tool_result` blocks immediately after: toolu_01A09q90qw90lq917835lq9. Each `tool_use` block must have a corresponding `tool_result` block in the next message.'}}

The mirror-image violation also occurs:

Output
'message': 'messages.2: unexpected `tool_use_id` found in `tool_result` blocks: toolu_01A09q90qw90lq917835lq9. Each `tool_result` block must have a corresponding `tool_use` block in the previous message.'

What it means

Tool calling is a strict conversational protocol. When the model wants to use a tool, its assistant message contains tool_use blocks, each with a unique id. Your job: run the tools, then send one user message whose content starts with a tool_result block for every id, matching exactly. The API validates the pairing on each request and rejects any conversation where a tool_use is left unanswered, answered late, or answered with an unknown id.

Why it happens

The pairing breaks in predictable ways: the assistant message with the tool_use blocks was not appended to history before sending the results; a plain-text user message was inserted between call and result; the model called several tools in parallel and only one got a result; results were sent with a stale or mistyped id; or an exception during tool execution skipped the result entirely — errors need results too.

How to fix it

1. Follow the canonical loop shape.

python
resp = client.messages.create(model=MODEL, max_tokens=1024,
                              tools=tools, messages=messages)

if resp.stop_reason == "tool_use":
    # 1) append the assistant turn UNCHANGED
    messages.append({"role": "assistant", "content": resp.content})

    # 2) build one tool_result per tool_use, ids copied
    results = []
    for block in resp.content:
        if block.type == "tool_use":
            output = run_tool(block.name, block.input)
            results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": str(output),
            })

    # 3) send them as the next user message
    messages.append({"role": "user", "content": results})
    resp = client.messages.create(model=MODEL, max_tokens=1024,
                                  tools=tools, messages=messages)

2. Answer every id, even on failure. A tool that crashed still owes a result:

python
results.append({
    "type": "tool_result",
    "tool_use_id": block.id,
    "content": f"Error: {exc}",
    "is_error": True,
})

The model reads the error and can recover; a missing result kills the whole conversation instead.

3. Keep tool_result blocks at the start of the user message content. Commentary text, if any, goes after them in the same message — never as a separate message in between.

4. When debugging, print the conversation skeleton.

python
for i, m in enumerate(messages):
    kinds = [b.get("type", "text") if isinstance(b, dict) else b.type
             for b in m["content"]] if isinstance(m["content"], list) else ["text"]
    print(i, m["role"], kinds)

The broken pair is visible immediately: a tool_use whose next message is not a user message leading with tool_result.

How to prevent it

Centralise history mutation in one function that appends call-and-result as an atomic pair — never let application code push messages directly mid-tool-loop. Test the multi-tool case early: parallel tool calls are where hand-rolled loops usually break first.