Claude tool_result Ordering Errors: Fix the Messages Sequence

Cover: a conceptual illustration of pairing calls with replies; it does not depict a required tool execution order or a verified API run.
For Claude’s native Messages API, return each client-side tool_result in the next user message after the assistant message containing its tool_use block. Match the call’s tool_use_id exactly, include one result per call, and put all result blocks before any text in that user message. When several tools are called together, return all their results together in that same follow-up message. Those are Anthropic’s documented native Messages rules. Tokenhot’s current route pages show a /v1/messages endpoint but do not document this detailed validation behavior, so first confirm that your request uses the native Anthropic message format before applying these checks to a Tokenhot error.
If you are debugging a native Claude API 400 with “tool_use ids were found without tool_result blocks immediately after,” first check whether every client tool call has a matching result in the very next user message. Anthropic documents this as a Messages formatting error. This is a native Anthropic example, not a Tokenhot incident observed for this article; Tokenhot’s public route examples do not establish route-specific validation. Anthropic’s handling guide gives the exact error text under its formatting requirements.
The native tool round trip in three messages
A client tool call is an assistant message containing one or more tool_use blocks. Your application runs those tools, then appends a user message containing the results. The next request includes the conversation history in order:
[
{"role": "user", "content": "What is the weather in Paris?"},
{
"role": "assistant",
"content": [
{"type": "tool_use", "id": "toolu_demo_1", "name": "get_weather", "input": {"location": "Paris"}}
]
},
{
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": "toolu_demo_1", "content": "12 C, cloudy"}
]
}
]
The ID above is illustrative. In a real request, copy the actual id returned in that assistant tool_use; don’t generate a replacement ID. Anthropic documents that the result’s tool_use_id matches the call ID and that the result message immediately follows the assistant tool-use message. Handle tool calls also explains that Claude’s native convention places tool_use in an assistant message and tool_result in a user message, rather than adding a separate tool role.
This ordering is invalid because it places text before the result block:
{
"role": "user",
"content": [
{"type": "text", "text": "The lookup finished:"},
{"type": "tool_result", "tool_use_id": "toolu_demo_1", "content": "12 C, cloudy"}
]
}
For a client-only tool turn, move the text after every result or put it in a later message. If the assistant turn also initiated an unresolved server tool, Anthropic documents a stricter branch: the client’s user reply must contain only the client tool_result blocks, with no text that would end the turn while that server call is pending. Server tools have their own response lifecycle, so don’t invent client tool_result blocks for them.
Handle parallel calls by ID, not by list position
Claude can return multiple tool_use blocks in one assistant response. Your application decides whether to run them concurrently or sequentially; the Anthropic API does not impose an execution order. Independent read-only calls may be run concurrently, while operations with side effects, shared state, or ordering requirements often need sequential handling. Whichever strategy you choose, collect one tool_result for each call and send them together in the next user message, matching by each call’s actual ID. Anthropic’s parallel-use guide documents this mapping and specifically says to return a result for a call you skipped, marking it is_error: true with an explanation.
[
{"role": "assistant", "content": [
{"type": "tool_use", "id": "toolu_demo_weather", "name": "get_weather", "input": {"location": "Paris"}},
{"type": "tool_use", "id": "toolu_demo_time", "name": "get_time", "input": {"timezone": "Europe/Paris"}}
]},
{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "toolu_demo_time", "content": "14:30 CEST"},
{"type": "tool_result", "tool_use_id": "toolu_demo_weather", "content": "12 C, cloudy"}
]}
]
The result order can differ if each result still points to the correct call ID; if your own orchestration expects one fixed order, preserve that expectation consistently. The two IDs and outputs above are hypothetical. A list-position match can silently attach the wrong output to the wrong tool when results complete in a different order.
A small local preflight can catch common transcript-shape mistakes before you submit a continuation. This does not call an API or prove Tokenhot accepts the request; it checks only client-tool result adjacency, count, and IDs in a Python list of message dictionaries:
def check_tool_result_pairs(messages):
def blocks(message, kind):
content = message.get("content", [])
if not isinstance(content, list):
return []
return [block for block in content
if isinstance(block, dict) and block.get("type") == kind]
def ids(block_list, field, label):
values = [block.get(field) for block in block_list]
if any(not isinstance(value, str) or not value.strip()
for value in values):
raise ValueError(f"{label} IDs must be nonempty strings")
if len(values) != len(set(values)):
raise ValueError(f"duplicate {label} ID")
return values
def check_pair(calls, user_message):
user_content = user_message.get("content", [])
if not isinstance(user_content, list):
raise ValueError("tool_result content must be a block list")
results = []
saw_non_result = False
for block in user_content:
if isinstance(block, dict) and block.get("type") == "tool_result":
if saw_non_result:
raise ValueError("tool_result blocks must come before other content")
results.append(block)
else:
saw_non_result = True
expected = ids(calls, "id", "tool_use")
actual = ids(results, "tool_use_id", "tool_result")
if set(expected) != set(actual) or len(expected) != len(actual):
raise ValueError("each preceding tool_use needs exactly one matching result")
for index, message in enumerate(messages):
if message.get("role") == "assistant":
calls = blocks(message, "tool_use")
if calls:
if (index + 1 >= len(messages)
or messages[index + 1].get("role") != "user"):
raise ValueError("tool_result must be in the next user message")
check_pair(calls, messages[index + 1])
if message.get("role") == "user":
results = blocks(message, "tool_result")
if results:
if (index == 0
or messages[index - 1].get("role") != "assistant"):
raise ValueError("tool_result has no immediately preceding assistant turn")
preceding_calls = blocks(messages[index - 1], "tool_use")
# Compare even when preceding_calls is empty: this rejects orphans.
check_pair(preceding_calls, message)
This small preflight assumes a simplified native client-tool transcript. It does not check tool schemas, server-tool lifecycle, special toolsets, or gateway transformations. Walk through the actual outgoing history as well, especially if a retry path mutates or reconstructs message content.
Return failures as results; keep execution state separate
A tool can fail even when the message sequence is correct. For a client tool that ran and returned an error, send its result with is_error set to true and a short, useful explanation such as “Inventory lookup timed out; no reservation was submitted.” Anthropic’s docs recommend describing what failed and what Claude can try next, rather than returning a generic failed. If your orchestration deliberately skipped a call, the parallel-tool guide likewise says to return an error result for that call so the assistant turn is accounted for.
An interrupted worker is a different case. Your process may have sent a side-effecting operation and lost the response before writing a tool_result into conversation history. Before you repeat the operation, check a durable call record using the provider/tool request ID, idempotency key, or application state if available. Then either recover the stored outcome and return it against the original tool_use_id, or report a truthful error/unknown outcome. Do not rerun a payment, deletion, reservation, email, or other non-idempotent action just because the model transcript has no result block; missing conversation state does not prove the tool never ran. If your tool has no way to check or safely deduplicate the operation, pause for reconciliation instead of guessing.
Keep the assistant response as received when replaying the conversation, especially if it also contains thinking blocks. Anthropic’s thinking/tool round-trip example says to echo the assistant content unchanged when returning the tool result. This helps keep two separate protocol obligations intact: preserve model response blocks that need preserving, and pair each tool_use with its own result.
If the route still rejects the continuation
First classify where the failure arose:
- Malformed transcript: A result is missing, duplicated, references another call ID, appears in an intervening message, or follows user text in the same content array.
- Tool execution failure: The tool ran but returned an error; report it as a result with is_error: true rather than deleting the call from history.
- Interrupted or persisted operation: The worker’s side-effect status is unknown; reconcile or deduplicate before rerunning.
- Different wire protocol: An OpenAI-compatible gateway layer may translate tools to another message format. Use the gateway’s documented contract for the actual request path; native Anthropic message ordering alone does not establish the translated schema.
- Unexplained route response: Keep the raw request and response ID, remove credentials/private content from any support example, and ask the provider whether the exact route and model code supports the message sequence you sent.
Tokenhot’s current Claude route entries list POST https://api.tokenhot.ai/v1/messages and model codes such as claude-sonnet-5, claude-opus-4-6, and claude-opus-4-8. Their visible examples show basic messages, not multi-tool histories or gateway handling of tool_result validation. See the current Sonnet 5, Opus 4.6, and Opus 4.8 entries for their stated endpoint and model code. These pages do not show that a Tokenhot request generated this error, nor do their labels establish version equivalence with Anthropic’s current documentation.
Before resending
- Every client tool_use ID has exactly one tool_result with the same tool_use_id.
- The results are in the immediately following user message and precede any text.
- Parallel results are mapped by ID, not assumed completion order.
- Failed or skipped calls have an explicit error result; they are not silently dropped.
- Tool execution state has been checked before repeating an operation with side effects.
- The request is known to use the native Anthropic Messages convention—or the actual translated protocol’s own documentation has been checked.
No live Tokenhot request or end-to-end tool run was performed for this guide. The examples are hypothetical payload patterns; the local preflight can check their shape but cannot establish remote compatibility or execution.
Fix missing or misplaced Claude tool results by matching every tool_use ID in the next user message, including parallel and failed calls.


