anomalyco / anomalyco/opencode

Failed parallel tool call leaves dangling tool_call_id, causing 400 errors on all subsequent requests

Open
#44,852 1 comment 0 reactions 1 assignee View on GitHub

@neriousy is already working on this.

Since Aug 25, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Description

When a batch of parallel tool calls contains a failing tool (e.g. an MCP server returning 403), opencode does not write a tool-result message for the failed tool's tool_call_id. The session history then contains an assistant message with tool_calls that has no corresponding role:"tool" response, which violates the OpenAI chat-completions protocol.

Every subsequent request in that session replays the malformed message array and is rejected by protocol-strict providers with HTTP 400. Automatic retries cannot recover because they resend the same broken history.

Error message

400 BadRequestError
Invalid request: an assistant message with 'tool_calls' must be followed by tool messages
responding to each 'tool_call_id'.
The following tool_call_ids did not have response messages:
zammad_nube_zammad_get_article_attachments:2

(Observed against a vLLM-hosted model, Kimi-K3 via LiteLLM proxy. The vLLM behavior is correct per the OpenAI spec — the client is sending an invalid messages array.)

Reproduction

  1. Configure an MCP tool that fails (e.g. returns 403 / auth error) alongside a working tool.
  2. Prompt the model so it issues parallel tool calls including both tools.
  3. One tool call fails (in my case another parallel call litellm_admin_litellm_model_list returned 403 and interrupted result flushing; the sibling call zammad_nube_zammad_get_article_attachments:2 never got a result message).
  4. Continue the conversation. Every following request fails with the 400 above; retries (3x) all fail identically. Recovery only happens after the conversation history is rebuilt.

Expected behavior

  1. Tool result flushing should be all-or-nothing per batch: even a failed tool call must produce a tool-result message, e.g. {"error": "403 Auth failed: ..."} — models handle error-text results gracefully.
  2. If a tool call is aborted/times out before producing a result, persist a status:"error" result part instead of nothing.
  3. Defense-in-depth: when serializing session parts to provider messages, synthesize a placeholder tool message for any dangling tool_call_id so old/affected sessions cannot poison future requests.

Environment

  • opencode 1.18.21 (per User-Agent opencode/1.18.21 ai-sdk/provider-utils/4.0.38 runtime/bun/1.3.14)
  • Provider: hosted vLLM (Kimi-K3) behind LiteLLM proxy
  • Tools involved: stdio MCP servers (zammad, litellm_admin)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.