Skip to content

Parallel tool calls: slow remote MCP tool stays state.status="running" forever; assistant message finish: null #31235

Closed as not planned

Description

@AntonioPrime

Summary

When the model emits two or more tool calls in the same assistant turn, and at least one of them is a remote MCP tool whose execution blocks for ~60s or more, the slow tool's part is created with state.status="running" and never gets updated to completed — even though the MCP server has successfully written the response back over a still-alive HTTP connection. The parent assistant message ends up with finish: null (no time.completed) — the turn is silently abandoned.

Single-tool-call turns to the same MCP server work correctly: same tool, same arguments → state.status="completed", finish: "tool-calls".

Environment

  • opencode version: 1.14.40
  • Mode: remote / serve, behind a reverse proxy with a generous (1h) idle timeout (verified — connection is not killed by the proxy)
  • Multiple remote MCP servers configured in opencode.json, all over streamable HTTP

Reproduction

  1. Configure two remote MCP servers in opencode.json:
    • A — any MCP with fast tools (sub-second), e.g. anything that does a single read.
    • B — any MCP with a tool that blocks for ~60s+ before returning (e.g. a wait_for_external_job that polls some upstream until terminal status).
  2. Send the model a prompt that naturally produces a turn with both tools in one assistant message — e.g. “Wait for the build, and while you wait, also check the cluster state.” The model emits:
    • B.wait_for_something(timeout_seconds=1800, …) — long blocking call
    • A.quick_check(…) — fast call
  3. Observe the sqlite DB:
    • message.data.finish for that assistant turn stays null, no time.completed.
    • part for A.quick_check → state.status="completed", state.output populated. ✓
    • part for B.wait_for_something → state.status="running", raw="", state.output absent — forever, even after server B writes its full response over the alive HTTP connection.
  4. Control: run the same prompt forcing the model to issue B.wait_for_something alone (no parallel sibling). Result: state.status="completed", finish: "tool-calls". ✓

Evidence

Two independent reproductions on our deployment, plus five clean control runs (IDs scrubbed):

run parallel? sibling outcome slow tool status message finish
1 yes fast MCP A tool, completed 102 ms running (stuck) null
2 yes fast MCP A tool, completed 27 ms running (stuck) null
3 no — completed tool-calls
4 no — completed tool-calls
5 no — completed tool-calls
6 no — completed tool-calls
7 no — completed tool-calls

5/5 single-tool runs ✓, 0/2 parallel-with-fast-sibling runs ✓. 100% correlation.

Server-side of the slow MCP B confirms the tool's loop ran to terminal status and the response was written to the still-alive stream (no EPIPE/ECONNRESET on the server, total request duration matches expected ~60-120 s). The byte path B → opencode is intact; the failure is in opencode's tool-result router.

Suspected

Some condition in the assistant-turn finalizer fires after the first tool result lands (the fast sibling) and the turn gets marked as "no longer pending tool calls" — the MCP-response listener for the slow callID is dropped before the slow tool returns. The slow tool result, when it arrives, has nowhere to land, so the part is never updated and the message never gets a finish.

Related but not duplicate

Workaround (for prompt authors)

Instruct the model in the slow tool's description (or in the system/agent prompt) to never call the long-running tool alongside other tool calls in the same turn. We saw the issue disappear after adding such guidance.

Activity

  1. github-actions commented on Aug 7, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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