Repository navigation
tools: empty-input tool calls execute and hang the session forever (shell and write) #54683
Description
Summary
The model occasionally emits a tool call whose input is completely empty (e.g. shell with an empty command, write with {}). Instead of rejecting it, the provider executes it; the call never completes, no completion/failure event is published, and the session hangs in "running" state forever. Only POST /api/session/:id/interrupt or a full service restart clears it.
Environment
- opencode version: 2.0.26 (desktop distribution,
opencode-cli.exe serve --service) - OS: Windows 10, 10.0.26300 (win32 x64)
- Terminal: Unavailable: headless/server usage — driven via the HTTP API
- Shell:
C:\WINDOWS\system32\cmd.exe - Install/channel: latest (desktop app, service mode)
- Active plugins:
auto-resumeis referenced in config but missing from disk (not loaded); no other plugins
Reproduction
The empty-input emission itself is model-side and cannot be forced deterministically (observed with glm-5.3 / glm-5.3#max via zai-coding-plan), but the resulting hung state is fully deterministic and easy to inspect:
- A session's turn goes quiet mid-work: no new messages arrive, and
GET /api/session/activereports that session as{ "type": "running" }indefinitely. GET /api/session/:id/message?limit=1&order=descreturns an assistant message whose last part is atool_use(shellorwrite) with empty input and notoolResultfollowing it, ever.- The state persists indefinitely (observed 20+ minutes, multiple times, until manually interrupted or the service restarts).
Expected Behavior
Tool providers validate input before executing: an empty command / empty write payload should return an immediate error tool-result ("command cannot be empty" / "content required"), so the model retries with complete input as part of the normal tool-error loop.
Actual Behavior
shellwith empty input: executes as an interactive shell that never exits — the tool call stays unsettled forever.writewith empty input: the call similarly never completes.- No
session.execution.succeeded/failed/interruptedevent is ever published, so liveness reporting claims the session is running forever, and nothing reaps it.
Additional Context
- 6+ occurrences between 2026-10-10 and 2026-10-12 across unrelated sessions and tasks; both
shellandwritevariants. - Workaround in the meantime: an external watchdog detects the signature (last message =
tool_usewith empty input, silent for 60s), interrupts the session, and re-prompts it to continue. Reliable, but in-flight turn work is lost each time. - Related: Provider stream can disappear while tool call remains permanently "running" with no timeout/reaper #51291 (provider stream disappearing leaves tool call permanently "running", no reaper), Tool calls have no timeout - one unsettled tool hangs the step forever (
readhas no bound while grep/glob/webfetch do) #53433 (no timeout on unsettled tool calls), tool calls: arguments corrupt across calls and schema-invalid calls can execute #47902 (schema-invalid tool calls can execute).
Activity
opencode-agent commentedon Oct 12, 2026
Thanks for the detailed report. I tried to reproduce this on 2.0.26 (Linux, simulated model), but every case I tried finished on its own:
shellwith{"command":""},shellwith{},writewith{}): the{}calls return an immediateInvalid arguments for tool … Missing keyerror. The empty command finishes straight away. The model is then called again as usual.tool_usewith empty input, sessionrunning). After the default 5-minutechunkTimeoutit ends with "Timed out waiting for response data", retries, and continues.The empty input you see is probably not a complete call that ran. It's more likely the placeholder recorded when a tool call starts streaming, which means the provider stream stopped before the arguments arrived. Even then, I'd expect recovery after about 5 minutes, not 20+.
These would help us narrow it down:
opencode export <sessionID>(orGET /api/session/:id/message) for one stuck session, showing the full stuck tool part (state, input, timestamps).chunkTimeout/timeout/headerTimeoutyou set in the provider settings for zai-coding-plan.Related issues (ordered by likelihood):
readhas no bound while grep/glob/webfetch do) #53433: tool calls have no timeout, so one stuck call hangs the stepBooyaka101 commentedon Oct 12, 2026
Your placeholder theory is confirmed — the call never executed.
I pulled the full stuck part from the 23-minute incident, and it backs your reading, not my original framing:
executed: falseon the tool part, and norantimestamp (the completed call 45 seconds earlier has one) — so nothing was ever run; the{}was indeed the streaming placeholder.{"id":"msg_129305ec7…","time":{"created":1791692398290,"streamed":1791693869336,"completed":1791693869339}, "type":"assistant","model":{"id":"glm-5.3","providerID":"zai-coding-plan"}, "content":[ {"type":"text","text":"All ten checks pass. Merging and dispatching the crawl to prove the pipeline end-to-end:"}, {"type":"tool","id":"call_e0c2121c…","name":"shell","executed":false, "state":{"status":"error","input":{},"error":{"type":"aborted","message":"Tool execution interrupted"}}, "time":{"created":1791692400445,"completed":1791693869338}}], "finish":"error","error":{"type":"aborted","message":"Step interrupted"}}(Caveat: the
abortedstate is my watchdog's interrupt — I've been clearing these manually/automatically. Pre-interrupt, the part sat with empty input and no completion. The timestampscreated→completedbracket the frozen window.)Your four questions:
writevariant, same provider) shows the identical shape.Timed out waiting for response data, noConnection lost while reading, no retry entries — anywhere. Possibly relevant: the desktop app's log folders under%APPDATA%\ai.opencode.desktop\logsstop getting written after Oct 10 even though the service ran (and restarted) through the incidents, so service-side log coverage may itself be a gap.chunkTimeout/timeout/headerTimeoutset anywhere — pure defaults for zai-coding-plan.space-bunny-free/fledge-alpha-freeoccasionally with no hangs observed, but the sample is small.The part that doesn't match your simulation: case 3 in your testing recovered via
chunkTimeoutafter ~5 minutes. Ours never did — 24m29s and 31m+ with the sessionrunning, no timeout, no retry, no log line, until interrupted externally. So whatever fires in the simulated silent-stream path doesn't seem to fire in this one — which points at the #51291 family (stream disappears, nothing reaps the call) rather than an argument-validation issue. Retitling this issue to match the real mechanism is reasonable by me; happy to do it if useful.For what it's worth: an external watchdog keyed on exactly this signature (last message =
tool_usewith empty input + no stream activity for 60s → interrupt → re-prompt) has absorbed ~6 occurrences reliably, if that's useful signal for a built-in reaper.opencode-agent commentedon Oct 12, 2026
Thanks for pulling the full part. That settles it:
executed: falsewith norantime means the{}is the placeholder created when a tool call starts streaming. Nothing was run, so argument validation isn't the problem. What's left is that the glm-5.3 stream stops sending tool arguments partway through the call, never ends, and nothing in OpenCode clears it.Why my simulated stall recovered and yours didn't: the 5-minute
chunkTimeout("Timed out waiting for response data") is reset by any bytes from the provider. This is a guess, but if zai-coding-plan keeps sending keepalive or empty events while the model is stuck, that timer never fires. That fits your logs having no timeout or retry entries at all.Possible workaround to try: set an overall request timeout for the provider. It has no default and limits the whole streamed response, keepalives or not. When it fires the request fails with "Timed out waiting for the response to complete", which should be retried like the other timeouts. In your config:
{ "providers": { "zai-coding-plan": { "settings": { "timeout": 900000 } } } }Pick a value above your longest normal turn (15 minutes above), because it also ends legitimately long responses. If it works, it should replace your external watchdog. If it doesn't, please tell us.
With the validation angle ruled out, this looks like the same problem as #51291 (provider stream stops mid-turn, the tool call stays "running", nothing clears it). Related issues, most likely first:
readhas no bound while grep/glob/webfetch do) #53433: no deadline on unsettled tool calls or steps, so one stuck call hangs the sessionIf you agree, I'd treat this issue as a duplicate of #51291 and keep the discussion there. If you think something here isn't covered by it, reply and say what.
opencode-agent commentedon Oct 13, 2026
Closing as a duplicate of #51291, since we haven't heard back. Your export showed the empty-input
shell/writepart was never run: it's the placeholder created when a tool call starts streaming. The glm-5.3 stream then stalled without ending, and nothing cleared the call. That's the problem tracked in #51291.Your export and the zai-coding-plan / glm-5.3 details would help over there. #53433 (no deadline on unsettled tool calls) is related too. The overall provider
timeoutsetting I suggested above may work around it in the meantime.If you find something here that #51291 doesn't cover, please reply and we can reopen.