Repository navigation
Parallel tool calls: slow remote MCP tool stays state.status="running" forever; assistant message finish: null #31235
Description
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
- 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_jobthat polls some upstream until terminal status).
- 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 callA.quick_check(…)— fast call
- Observe the sqlite DB:
message.data.finishfor that assistant turn staysnull, notime.completed.partforA.quick_check→state.status="completed",state.outputpopulated. ✓partforB.wait_for_something→state.status="running",raw="",state.outputabsent — forever, even after server B writes its full response over the alive HTTP connection.
- Control: run the same prompt forcing the model to issue
B.wait_for_somethingalone (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
- MCP tool completes successfully but session never resumes or finalizes in serve/web mode #22156 — same end state (tool completes on the server, session never resumes) but their trigger is a single tool, not a parallel pair.
- Tool and task execution can hang indefinitely with no timeout #20096 — generic "no timeout on tool execution"; closed without a fix.
- [P2] Parallel tool execution engine #24764 — parallel-execution engine RFC; closed as not planned. Notable: the RFC implies tools are dispatched sequentially, yet in our case two MCP calls clearly land in the DB within ~20 ms of each other — they are concurrent at the MCP-client layer even if the runner thinks they aren't.
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
github-actions commentedon Aug 7, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.