You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
Driven over the REST API: POST /session → POST /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.
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)
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.
Hypothesis for why this is deterministic here and intermittent elsewhere: the local provider streams at a very consistent rate (~83 tok/s, low jitter), so the two tool calls in a turn land at a highly repeatable interval — if the drop is a timing race in dispatch, a low-jitter emitter hits the vulnerable window every time, while cloud APIs (batched/jittery delivery) hit it occasionally.
Reviewed v1.17.19 and v1.17.20 release notes before filing: no changes to tool dispatch, session state, or serve mode, consistent with identical reproduction on both tested versions.
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
Run opencode serve with any OpenAI-compatible provider (reproduced with llama.cpp llama-server, local 35B model; low-jitter local providers trigger it 100%)
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.
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
Description
In
opencode servemode, 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}/abortis 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 serve), both reproduce identically@ai-sdk/openai-compatible) → llama.cppllama-server, local 35B model, ~83 tok/s streamingPOST /session→POST /session/{id}/prompt_async→ pollGET /session/statusReproduction
Attached script (
zombie-test.sh) runs N control sessions then N trigger sessions, sequentially, 120s limit each.Control prompt (never triggers):
Trigger prompt (triggers every time):
Results
Every stuck session shows the identical fingerprint: message log contains 2 tool parts, exactly 1 frozen at
"running", and/tmp/zt-N/awas never created on disk.Evidence from a stuck session (original field incident)
Message log tail — mkdir "running" forever, sibling completed in 17ms:
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
ssandps) — 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
Run
opencode servewith any OpenAI-compatible provider (reproduced with llama.cppllama-server, local 35B model; low-jitter local providers trigger it 100%)Via REST: create a session (
POST /session), thenPOST /session/{id}/prompt_asyncwith:Poll
GET /session/status— the session staysbusyforever; the message log shows one tool part frozen at"running";/tmp/zt-1/ais 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)