Skip to content

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

Closed as not planned

Description

@Joe-Ng-1998

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)

Activity

  1. github-actions commented on Aug 25, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  2. nowzycc commented on Oct 1, 2026

    @nowzycc

    Confirmed still present on the latest release, v2.0.21 (reproduced 2026-10-01; v2.0.21 is the newest v2 tag).

    Additional trigger, possibly distinct from the original MCP-403 case: the tool call was aborted because the command it ran restarted the managed background service (opencode service unset env <VAR>). On resume, a new step was dispatched before the aborted call's result was paired, so the request carried an assistant message with tool_calls and no matching tool message. Sessions without an interruption never reproduced it. Filed with the local evidence as #52452.

    For reference, the failure is the strict OpenAI-compatible 400:

    An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. (insufficient tool messages following tool_calls message)
    

    Lenient providers (including Anthropic) accept the same history.

    Note that the closest other reports are no longer actionable trackers: #49911 was withdrawn by its author, #22808 was auto-closed as stale, and #21326 only fixed the Anthropic path back in April. As far as I can tell this issue is the only open one for the OpenAI-compatible signal, so please keep it open unless it is genuinely superseded.

  3. thdxr commented on Oct 11, 2026

    @thdxr
    Member

    Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.

    — from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions