Home ยป Parallel Tool Calls and How They Fail
Parallel Tool Calls and How They Fail

Parallel Tool Calls and How They Fail

Figures on this page were verified 18 October 2026 against the providers' own documentation. Pricing, context windows and rate limits change without notice, so confirm any number against the provider before you rely on it. Tell us if something here is out of date.

Parallel tool calls fail almost exclusively on the response side, not the request side: the model calling several tools at once works by default, but returning results for them is where mismatched counts, wrong ordering, and malformed messages cause a 400 error. Both providers allow multiple tool calls in a single turn without you asking for it, and the entire failure surface is in how you reply.

It is on by default on both providers

Anthropic: by default, Claude may call multiple tools in a single response, and the API does not prescribe an execution order for you to run them in. OpenAI: with tool_choice: "auto", the model may call zero, one, or multiple functions. Neither API requires an opt-in flag to get parallel calls; if your code only ever handled one tool_use block per response, it was working by luck on simple prompts, not by design.

Rule 1: one result per call, no exceptions

Whichever execution strategy you use, concurrent, sequential, or anything else, you must return exactly one result for every tool call the model made in that turn, even for calls you decided not to run.

{
  "type": "tool_result",
  "tool_use_id": "toolu_02",
  "is_error": true,
  "content": "Not executed: the preceding write_file call failed."
}

If you ran three calls sequentially and the first one failed, you cannot simply omit results for the two you chose to skip. On Anthropic, a missing result produces a 400 error on the next request. We have not verified how OpenAI reports a missing function output, so check its behaviour before relying on it.

Rule 2: match on the ID, never on position

Both APIs hand you an identifier on every call and require you to echo it back on the matching result: tool_use_id on Anthropic, call_id on OpenAI’s Responses API. Do not rely on position: every result must carry the correct ID, and the ID is what ties it to its call.

# Anthropic
results = [
    {"type": "tool_result", "tool_use_id": call.id, "content": run_tool(call)}
    for call in tool_use_blocks   # match by call.id, never by list index
]

# OpenAI Responses API
results = [
    {"type": "function_call_output", "call_id": call.call_id, "output": run_tool(call)}
    for call in function_calls    # match by call.call_id
]

The bug this guards against is subtle: if you execute calls concurrently with asyncio.gather or Promise.all and the results resolve out of order, zipping them back against the original call list by position silently pairs the wrong result with the wrong call. Nothing errors. The model simply receives incorrect information attributed to the wrong tool, and produces a confidently wrong answer built on it.

Rule 3: formatting order inside the message, on Anthropic specifically

Anthropic enforces two structural rules that will 400 if violated. Every tool_result block must come before any text block in the same user message, and tool results must immediately follow the assistant’s tool-use message with nothing in between.

// WRONG: text before tool_result triggers a 400 error
{
  "role": "user",
  "content": [
    {"type": "text", "text": "Here are the results:"},
    {"type": "tool_result", "tool_use_id": "toolu_01", "content": "..."}
  ]
}

// RIGHT: every tool_result first, text (if any) after
{
  "role": "user",
  "content": [
    {"type": "tool_result", "tool_use_id": "toolu_01", "content": "..."},
    {"type": "tool_result", "tool_use_id": "toolu_02", "content": "..."},
    {"type": "text", "text": "What should I do next?"}
  ]
}

If you see “tool_use ids were found without tool_result blocks immediately after,” this ordering rule, or the one-result-per-call rule above, is almost always the cause.

Choosing concurrent vs sequential execution

The API leaves this entirely to you, and the right choice depends on what the tools do, not on the API.

Tool characteristicsRun
Independent, read-only (search, lookup, fetch)Concurrently, for lower latency
Writes, side effects, or shared stateSequentially, to avoid race conditions
One call’s output feeds another’s inputSequentially, in dependency order

Turning parallel calls off

If your tools have side effects that make concurrent or even multiple sequential calls unsafe in a single turn, disable parallel calling rather than defending against it after the fact.

# Anthropic: disable_parallel_tool_use lives inside tool_choice, not at the top level
{"tool_choice": {"type": "auto", "disable_parallel_tool_use": true}}

# OpenAI
{"parallel_tool_calls": false}

On Anthropic with tool_choice type auto, this caps the model at one tool call per response while still allowing a plain-text reply. On OpenAI, it guarantees zero or exactly one tool is called per turn.

Related errors

If a single call’s arguments will not parse, see tool call arguments that will not parse. If calls are being rejected for exceeding a count rather than mismatching, see tool call limit reached in a single turn. The full map is in the LLM API error reference.

Frequently asked questions

Are parallel tool calls on by default?

Yes on both providers. Anthropic may return several tool_use blocks in one response, and OpenAI auto allows zero, one or several function calls. No opt-in flag is needed.

Why do I get a 400 about tool_use ids without tool_result blocks?

On Anthropic every tool_use block needs a tool_result in the very next user message, all tool_result blocks must come before any text, and nothing may sit between the two messages. Return an is_error result for calls you chose not to run.

How do I turn parallel tool calls off?

On Anthropic set disable_parallel_tool_use to true inside the tool_choice object, not at the top level. On OpenAI set parallel_tool_calls to false.

Chirag Darji

Chirag Darji is the founder of VGraple and the editor of It's About You. He writes about the LLM APIs and developer tooling he works with, and every figure published here is checked against the provider's own documentation before it goes live, with the date it was verified shown on the page.

More Reading

Post navigation