Claude Parallel Tool Use Not Supported — Sequencing Fix (2026) | AI Error Hub
Anthropic Claude Tool Use Severity: Medium Design pattern

Claude Parallel Tool Use — Execution Errors

Claude returned multiple tool_use blocks in one response but your code only handles one. Or you need Claude to call tools sequentially and it's parallelizing. Fix: iterate all tool_use blocks and execute concurrently in your code, or use disable_parallel_tool_use.

Error surface: Client execution logic Category: Tool Use Feature: disable_parallel_tool_use Last tested: Nov 15, 2026
Quick Fix (TL;DR)

Claude may emit multiple tool_use blocks in a single assistant message when tools are independent. Iterate ALL of them, execute concurrently (asyncio.gather / Promise.all), and return ALL tool_results in one user message. To force sequential: set tool_choice: {type: "auto", disable_parallel_tool_use: true}.

The Symptoms You're Seeing

Common failure modes
Claude returned 3 tool_use blocks; my code called only the first
  → Result: missing tool_result IDs → next API call fails

Error: "tool_result for tool_use_id toolu_02 is missing"
  → You returned only 1 tool_result but Claude expects N

Agent slow because tools run one at a time
  → Concurrent execution possible; code doesn't take advantage

Race conditions when tools share state (writing to same file)
  → Parallel tools have dependency; must serialize

Second tool result depends on first tool's output
  → Claude split into two turns but should have chained

How Parallel Tool Use Works

Claude decides parallelism

When Claude sees multiple independent tools it could call, it may emit them all in one response. Your job is to execute them (in parallel for speed) and return all results. Claude only chains sequentially when later calls depend on earlier ones — that's automatic based on Claude's understanding.

Parallel tool_use response structure
response.content: [ { type: "text", text: "I'll check the weather in 3 cities." }, { type: "tool_use", id: "toolu_01", name: "get_weather", input: { location: "Karachi" } }, { type: "tool_use", id: "toolu_02", name: "get_weather", input: { location: "Lahore" } }, { type: "tool_use", id: "toolu_03", name: "get_weather", input: { location: "Islamabad" } } ] // Your next user message must include ALL three results: { role: "user", content: [ { type: "tool_result", tool_use_id: "toolu_01", content: "32°C" }, { type: "tool_result", tool_use_id: "toolu_02", content: "29°C" }, { type: "tool_result", tool_use_id: "toolu_03", content: "27°C" } ]}

What Actually Causes This Error

45%
Code assumes 1 tool_use per responseLoop reads first tool_use, ignores rest. Missing results = 400 on next call.
25%
Sequential execution when parallel possibleAwaiting each tool one by one; latency = sum instead of max.
15%
Race conditions with shared stateTwo parallel writes to same DB row; second overwrites first.
10%
Wanted sequencing, got parallelismTools have hidden dependency Claude couldn't detect.
5%
Not passing tool_use_id back correctlyResult IDs don't match the call IDs; Claude can't correlate.

Fixes That Work (Tested Nov 2026)

1Iterate All tool_use Blocks, Execute in Parallel

Python · asyncio.gather for concurrency
import asyncio from anthropic import AsyncAnthropic client = AsyncAnthropic() async def execute_tool(tool_use): """Async tool executor.""" if tool_use.name == "get_weather": return await fetch_weather(tool_use.input["location"]) elif tool_use.name == "web_search": return await search_web(tool_use.input["query"]) # ... more tools async def run_agent(messages): while True: response = await client.messages.create( model="claude-sonnet-4-5", max_tokens=4096, tools=my_tools, messages=messages ) messages.append({"role": "assistant", "content": response.content}) if response.stop_reason == "end_turn": return response # Gather ALL tool_use blocks tool_uses = [b for b in response.content if b.type == "tool_use"] # Execute in parallel — huge speedup for N>1 results = await asyncio.gather( *[execute_tool(tu) for tu in tool_uses], return_exceptions=True ) # Build tool_result blocks — all in one user message tool_results = [] for tu, result in zip(tool_uses, results): if isinstance(result, Exception): tool_results.append({ "type": "tool_result", "tool_use_id": tu.id, "content": f"Error: {result}", "is_error": True }) else: tool_results.append({ "type": "tool_result", "tool_use_id": tu.id, "content": str(result) }) messages.append({"role": "user", "content": tool_results})

2Force Sequential With disable_parallel_tool_use

Python · when tools have hidden dependencies
# Tools that must run sequentially: # - create_file then edit_file # - place_order then charge_payment # - deploy then verify response = client.messages.create( model="claude-sonnet-4-5", max_tokens=4096, tools=my_tools, tool_choice={ "type": "auto", "disable_parallel_tool_use": True # ← forces one tool per turn }, messages=messages ) # Now Claude will only emit one tool_use block per response # Executes serially — good for stateful workflows
Also works with tool_choice: any or specific tool

The disable_parallel_tool_use flag can pair with all tool_choice modes: auto, any, or {type: "tool", name: "specific"}. Useful when you want to force a specific tool AND ensure only one call per turn.

3Handle Shared State With Locks

Python · lock per resource for safe parallel
import asyncio from collections import defaultdict # Per-resource locks resource_locks = defaultdict(asyncio.Lock) async def execute_tool_safe(tool_use): """Wrap execution with lock if tool touches shared state.""" # Identify resource being modified resource_key = None if tool_use.name == "update_file": resource_key = f"file:{tool_use.input['path']}" elif tool_use.name == "update_row": resource_key = f"row:{tool_use.input['table']}:{tool_use.input['id']}" if resource_key: async with resource_locks[resource_key]: return await execute_tool(tool_use) else: return await execute_tool(tool_use) # Now parallel is safe — same resource gets serialized # Different resources run truly concurrent results = await asyncio.gather(*[execute_tool_safe(tu) for tu in tool_uses])

Preventing This Error Going Forward

  • Always iterate ALL tool_use blocks. Never assume one per response.
  • Use asyncio.gather / Promise.all for concurrency. Free performance win.
  • Add locks around shared state. Prevents race conditions.
  • Force sequential when order matters. Use disable_parallel_tool_use for workflows.
  • Return one result per tool_use. IDs must match; count must match.
  • Use return_exceptions=True. One failure shouldn't kill the whole batch.
AR
Tested by Ahmed R.
Researcher · AI Error Hub
Last verified: Nov 15, 2026 · SDK: anthropic 0.42

Frequently Asked Questions

Usually yes, but not guaranteed. Claude's decision depends on the task. Some models parallelize more aggressively than others. Test empirically for your workload; use disable_parallel_tool_use if you need deterministic behavior.

No. Each tool_use is generated during Claude's output. Whether they're in one response (parallel) or spread across turns (sequential) doesn't change token cost — the tokens are identical either way.

API rejects the request with 400. Every tool_use_id must have a matching tool_result. If a tool fails, return an error result with is_error: true — don't skip.

Yes. Order in the array doesn't matter — Claude matches by tool_use_id. But it's conventional to return them in the same order for readability.

You'll see multiple content_block_start events for tool_use blocks. Wait for all content_block_stop events to have complete inputs, then execute all in parallel. Don't kick off tools mid-stream.