Skip to content

tools: empty-input tool calls execute and hang the session forever (shell and write) #54683

Closed as duplicate

Description

@Booyaka101

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-resume is 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:

  1. A session's turn goes quiet mid-work: no new messages arrive, and GET /api/session/active reports that session as { "type": "running" } indefinitely.
  2. GET /api/session/:id/message?limit=1&order=desc returns an assistant message whose last part is a tool_use (shell or write) with empty input and no toolResult following it, ever.
  3. 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

  • shell with empty input: executes as an interactive shell that never exits — the tool call stays unsettled forever.
  • write with empty input: the call similarly never completes.
  • No session.execution.succeeded / failed / interrupted event is ever published, so liveness reporting claims the session is running forever, and nothing reaps it.

Additional Context

Activity

  1. opencode-agent commented on Oct 12, 2026

    @opencode-agent
    Contributor

    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:

    • Complete tool calls with empty input (shell with {"command":""}, shell with {}, write with {}): the {} calls return an immediate Invalid arguments for tool … Missing key error. The empty command finishes straight away. The model is then called again as usual.
    • A tool call that starts streaming (empty input, "Writing command…") and then the stream drops: it ends with "Connection lost while reading the response" and is retried.
    • The same call, but the stream goes silent without disconnecting: this looks exactly like what you describe (last part is a tool_use with empty input, session running). After the default 5-minute chunkTimeout it 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:

    1. The output of opencode export <sessionID> (or GET /api/session/:id/message) for one stuck session, showing the full stuck tool part (state, input, timestamps).
    2. The server log around the time it went quiet. In particular: is there any "Timed out waiting for response data" or retry entry, or does the log just stop?
    3. Any chunkTimeout / timeout / headerTimeout you set in the provider settings for zai-coding-plan.
    4. Whether you've seen it with any provider other than zai-coding-plan.

    Related issues (ordered by likelihood):

  2. Booyaka101 commented on Oct 12, 2026

    @Booyaka101
    Author

    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: false on the tool part, and no ran timestamp (the completed call 45 seconds earlier has one) — so nothing was ever run; the {} was indeed the streaming placeholder.
    • Timeline: assistant text streamed fine ("All ten checks pass. Merging…"), tool part created 2s later with empty input, then silence for 24m 29s until an external interrupt cleared it.
    {"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 aborted state is my watchdog's interrupt — I've been clearing these manually/automatically. Pre-interrupt, the part sat with empty input and no completion. The timestamps created → completed bracket the frozen window.)

    Your four questions:

    1. Above. The other long incident (31 min, write variant, same provider) shows the identical shape.
    2. Server logs: no Timed out waiting for response data, no Connection lost while reading, no retry entries — anywhere. Possibly relevant: the desktop app's log folders under %APPDATA%\ai.opencode.desktop\logs stop 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.
    3. No chunkTimeout / timeout / headerTimeout set anywhere — pure defaults for zai-coding-plan.
    4. All 6+ incidents are zai-coding-plan / glm-5.3 family. We run space-bunny-free / fledge-alpha-free occasionally with no hangs observed, but the sample is small.

    The part that doesn't match your simulation: case 3 in your testing recovered via chunkTimeout after ~5 minutes. Ours never did — 24m29s and 31m+ with the session running, 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_use with 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.

  3. opencode-agent commented on Oct 12, 2026

    @opencode-agent
    Contributor

    Thanks for pulling the full part. That settles it: executed: false with no ran time 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:

    If 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.

  4. opencode-agent commented on Oct 13, 2026

    @opencode-agent
    Contributor

    Closing as a duplicate of #51291, since we haven't heard back. Your export showed the empty-input shell/write part 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 timeout setting 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions