Skip to content

Serve mode: multi-tool-call turn silently drops one bash dispatch; session stuck "busy" forever (deterministic repro: 10/10 on v1.17.18 and v1.17.20) #36804

Description

@Smerked

Description

In opencode serve mode, when the model emits two bash tool calls in a single response, one call executes and completes normally, while the other is never dispatched at all — no process is spawned, no filesystem effect occurs — yet its part state remains "status": "running" indefinitely. The session then reports {"type":"busy"} forever. POST /session/{id}/abort is the only recovery.

Key discriminator vs. similar reports: the dropped command was mkdir -p <path> and the directory was never created. This is not "tool completed but session failed to resume" (#22156) — the dispatch itself is lost. The sibling call from the same turn completes fine (17ms, normal exit).

Environment

  • opencode v1.17.18 and v1.17.20 (opencode serve), both reproduce identically
  • Ubuntu 24.04, x86_64
  • Provider: custom OpenAI-compatible (@ai-sdk/openai-compatible) → llama.cpp llama-server, local 35B model, ~83 tok/s streaming
  • Driven over the REST API: POST /sessionPOST /session/{id}/prompt_async → poll GET /session/status

Reproduction

Attached script (zombie-test.sh) runs N control sessions then N trigger sessions, sequentially, 120s limit each.

Control prompt (never triggers):

Call the bash tool exactly once with the command: echo control-1.
Do not call any other tools. Then stop and give a one-word answer.

Trigger prompt (triggers every time):

In one single response, call the bash tool TWICE (two separate tool
calls in the same response): first command: mkdir -p /tmp/zt-1/a
second command: echo done-1. Then stop and give a one-word answer.

Results

version | control (1 call/turn) | trigger (2 calls/turn) -- | -- | -- v1.17.18 | 10/10 completed | 10/10 stuck busy forever v1.17.20 | 10/10 completed | 10/10 stuck busy forever

Every stuck session shows the identical fingerprint: message log contains 2 tool parts, exactly 1 frozen at "running", and /tmp/zt-N/a was never created on disk.

Evidence from a stuck session (original field incident)

Message log tail — mkdir "running" forever, sibling completed in 17ms:

json
{ "type": "tool", "tool": "bash",
  "state": { "status": "running",
    "input": { "command": "mkdir -p .../toolchains/android-sdk/cmdline-tools && mkdir -p .../toolchains/android-sdk" },
    "time": { "start": 1783992687894 } } }
{ "type": "tool", "tool": "bash",
  "state": { "status": "completed",
    "input": { "command": "which java && java -version 2>&1" },
    "metadata": { "exit": 1 },
    "time": { "start": 1783992688311, "end": 1783992688328 } } }

Provider-side timeline (llama-server logs): the model turn that emitted both calls finished generating normally; the provider was never asked for another turn afterward. While the session sat "busy" there were zero established sockets to the serve process and no child processes (verified with ss and ps) — nothing was executing, nothing was in flight.

Notes

Happy to run patches or additional diagnostics against this setup — the repro takes ~10 minutes and triggers 100% of the time.

Plugins

None

OpenCode version

v1.17.18 and v1.17.20 — reproduced identically on both

Steps to reproduce

  1. Run opencode serve with any OpenAI-compatible provider (reproduced with llama.cpp llama-server, local 35B model; low-jitter local providers trigger it 100%)

  2. Via REST: create a session (POST /session), then POST /session/{id}/prompt_async with:

    In one single response, call the bash tool TWICE (two separate tool
    calls in the same response): first command: mkdir -p /tmp/zt-1/a
    second command: echo done-1. Then stop and give a one-word answer.
    
  3. Poll GET /session/status — the session stays busy forever; the message log shows one tool part frozen at "running"; /tmp/zt-1/a is never created on disk.

Attached script automates 10 control + 10 trigger sessions:
zombie-test.sh

Screenshot and/or share link

Placeholder@Placeholder-PC:~/TheProject/window-temp-android$ export $(sudo cat /etc/opencode-serve.env)
~/zombie-test.sh
=== CONTROL arm (1 call/turn) ===
[control 1] IDLE tools/running=1,0,ok dir=NA
[control 2] IDLE tools/running=1,0,ok dir=NA
[control 3] IDLE tools/running=1,0,ok dir=NA
[control 4] IDLE tools/running=1,0,ok dir=NA
[control 5] IDLE tools/running=1,0,ok dir=NA
[control 6] IDLE tools/running=1,0,ok dir=NA
[control 7] IDLE tools/running=1,0,ok dir=NA
[control 8] IDLE tools/running=1,0,ok dir=NA
[control 9] IDLE tools/running=1,0,ok dir=NA
[control 10] IDLE tools/running=1,0,ok dir=NA
=== TRIGGER arm (2 calls/turn) ===
[trigger 1] STUCK tools/running=2,1,ok dir=NO
[trigger 2] STUCK tools/running=2,1,ok dir=NO
[trigger 3] STUCK tools/running=2,1,ok dir=NO
[trigger 4] STUCK tools/running=2,1,ok dir=NO
[trigger 5] STUCK tools/running=2,1,ok dir=NO
[trigger 6] STUCK tools/running=2,1,ok dir=NO
[trigger 7] STUCK tools/running=2,1,ok dir=NO
[trigger 8] STUCK tools/running=2,1,ok dir=NO
[trigger 9] STUCK tools/running=2,1,ok dir=NO
[trigger 10] STUCK tools/running=2,1,ok dir=NO
=== SUMMARY ===
10 control IDLE
10 trigger STUCK
full log: /home/complex/zombie-test-results.txt

zombie-test-results.txt

Operating System

Ubuntu 24.04 (x86_64)

Terminal

N/A — headless serve mode, driven via REST API (no TUI involved)

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