Skip to content

API Error: 400 due to tool use concurrency issues. Run /rewind to recover the conversation

This is a request-validation failure, not a runtime one. The conversation being sent contains an assistant message with a tool_use block that no tool_result block answers, and the API rejects the whole message array before any model runs. That rules out the classes people check first: it is not your API key (that returns a 401), not billing or a quota, not the platform being busy (529), and not the model failing at the task — the model never saw the request. It is also not a spend limit you set yourself, even though that one is also a 400: that 400 arrives with a message beginning You have reached your specified API usage limits, and yours does not.

Data as of 2026-09. Vendor limits and defaults change; check the official docs for current values before acting on any number below.

Why the same 400 comes back forever

The Messages API is stateless. There is no conversation living on the server. Every turn, your client sends the entire history back as one array, and the server validates that array from the top before doing anything with it.

One of the invariants it checks is strict and positional: a user message that follows an assistant message containing tool_use blocks must begin with a matching number of tool_result blocks. Matching number, at the beginning — not somewhere later in the turn, not eventually.

Parallel tool calls are what make that fragile, and they are the “concurrency” the message is naming. A single assistant turn can emit several tool_use blocks at once, and every one of them has to be answered inside one following user message. If even one of those calls never produces a result — you pressed Esc, the process was killed, a tool hung and got cancelled, a subagent died — the client is holding fewer results than calls and has no legal shape to write.

The reason this is permanent rather than transient: the malformed array lives in your local transcript, and your client rebuilds the request from that file on every turn. Retrying does not produce a different request. It produces the identical one, and a deterministic validator handed identical input returns an identical answer. That is the entire explanation for a conversation that “gets stuck” — nothing in the retry path touches the thing that is actually wrong.

It also explains why continuing the conversation makes things worse instead of better. Typing a fresh instruction appends a user message that does not begin with tool_result blocks, which is exactly the violation. The instinctive recovery move is itself the error.

Is retrying useful?

No. An identical request fails identically, every time, until the history changes.

A 400 is an invalid_request_error — a format or content problem with what you sent. The official SDKs retry transient failures (connection errors, rate limits, 5xx) with exponential backoff, twice by default, honoring retry-after when it is present. A 400 is deliberately not in that set, and that is correct behavior rather than a gap: no amount of waiting turns a malformed array into a well-formed one.

Retry once if you want the proof. If the second attempt returns the same 400, you have confirmed a deterministic failure and everything below applies. A third attempt tells you nothing the second one did not. If you have wrapped your own retry loop around this call, exclude 400 from it — otherwise a single broken pair turns into a tight loop that re-uploads your whole conversation to be rejected again.

Which break you are actually looking at

The message text is the same regardless of how the pair got severed, so the signal is in what happened immediately before the first failure.

  • It started on the turn right after you interrupted. The common case. Open the transcript: the last assistant turn fired more than one tool, and the user turn after it is missing at least one result.
  • It started after a crash, an OOM kill, or closing the terminal mid-task. Same shape, different trigger — the process died between emitting tool_use and writing the results back.
  • It started right after an automatic context compaction. Compaction rewrites history. If its boundary falls between an assistant tool_use and the user message carrying the results, the pair is cut by the repair rather than by you. Check for no interrupt at all, with a compaction or summarization step as the last thing logged before the failure.
  • Two processes were writing the same session. The same session resumed in two windows, or a file-sync tool merging the transcript. Check whether you resumed in more than one place, or whether the session file sits on a synced drive. This is the case the word “concurrency” describes most literally.
  • You drive the API yourself and there is no CLI in the picture. Your loop appended a user message before every tool_use id was answered. Count the tool_use ids in the last assistant message and the tool_result ids in the message after it — they must match in count and in id.

What to actually do

  • Interrupt, crash, or compaction (any CLI case) — run /rewind, but not by one step. Rewind to a point before the assistant turn that emitted the unanswered tool_use. Rewinding to the nearest checkpoint often lands you still inside the broken region; the next request fails identically and people conclude rewind is broken when it simply did not go back far enough.
  • Checkpoints do not reach far enough back — start a new session and restate the task. Annoying, and still faster than the three non-fixes below.
  • Two writers on one session — close every other instance first, then rewind. Otherwise the other process writes the broken array back underneath you.
  • Your own API loop — either drop the trailing assistant message carrying the dangling tool_use, or synthesize what is missing: one tool_result per unanswered tool_use id, at the beginning of the next user message, flagged as an error result (is_error) saying the call was cancelled. Both restore the invariant; dropping loses the turn, synthesizing keeps it.

Three repairs that look right and are not

Restarting the CLI. Resuming reads the same transcript file back in. The array that failed is the array you return to. A restart changes nothing here, which is why this error survives the one move that fixes most others.

Switching models. Request validation happens at the API surface, before a model is selected to run inference. Every model rejects the same array for the same reason.

Hand-editing the last assistant message to delete the dangling tool_use. This is the clever fix, and it is specifically foreclosed. When extended thinking is on, thinking and redacted_thinking blocks in the latest assistant message cannot be modified — they have to go back exactly as they came out, or you get a different 400 complaining about thinking blocks. Truncating history is permitted; doctoring the most recent assistant turn is not. That asymmetry is the reason the recovery command is /rewind rather than an “edit history” command.

Confirming the conversation is healthy again

Do not immediately re-run the task that broke it — a task that fans out into parallel tools cannot distinguish “history is fixed” from “history is still broken but this time nothing got interrupted.”

Send a message that triggers no tools at all first: a plain question. If it returns normally, the array validates end to end. If it returns the same 400, your rewind did not go back far enough — rewind one more turn and repeat. That one short turn is the whole test, and it is falsifiable in a way that “try it and see” is not.

Then run one task that fires a single tool. Only once that succeeds should you go back to work that fans out. To keep it from recurring, land interrupts when nothing is in flight, or let the running calls finish before you cancel; the window that breaks a conversation is exactly the span between a parallel tool_use batch going out and its last result coming back.

If your 400 names a message index and says Did not find 1 tool_result block(s) at the beginning of this message, that is the same break stated in the validator’s own words — and the index tells you how far back in history to rewind, which this message does not. The model’s tool call could not be parsed is the opposite situation: there the call never became a valid tool_use at all, history stayed intact, and a retry is worth something. If you are not sure which of the tool-call failures you have, the AI coding error triage tool sorts them by the signals above — the deciding question is always whether your stored history is still valid.