Skip to content

session: truncated tool call left "streaming" — message undecodable (500), turn marked succeeded #52553

Closed

Description

@gandb

Description

When the model reaches the max output tokens (finish_reason: length) in the middle of a tool call, opencode
closes the turn as succeeded and persists the partial tool call with state.input as a truncated string.
The tool part then stays status: "streaming" forever and the session becomes unreadable:
GET /api/session/{id}/message returns HTTP 500. In the client the session stays "writing" — ESC does
nothing and input is not accepted — and the user cannot use that session at all.

Plugins

Local plugins, all three from config: ./plugins/opencode-anti-loop, ./plugins/sglang-guard,
./plugins/plur-memory. (Anti-loop had fired earlier in the same session for an unrelated reason; this defect
reproduces independently of it.)

OpenCode version

2.0.21 (opencode --version). The affected session was created under 2.0.20 — its session_v2.version is
still "2.0.20" and it is the only session in the DB not on 2.0.21.

Steps to reproduce

  1. Use a local OpenAI-compatible provider with a small output cap and heavy reasoning, e.g.
    limit.output: 19000, limit.context: 95000, variant with reasoning_effort: xhigh.
  2. Send a turn in which the model spends most of the output budget on reasoning and then starts a large
    write tool call (a long file).
  3. The provider ends the stream with finish_reason: length while the tool-call JSON is still incomplete.
    Measured on the affected turn: reasoning = 57,807 chars, tokens.output = 19000 (the cap), input = 48052.
  4. Observe the session stop there: turn idle with outcome: succeeded, the file was never written, and the
    tool call never executed.

Expected behaviour

  • A tool call that did not execute must not be persisted mid-flight: either discard it or persist it as an
    error with time.completed.
  • The turn must not be reported as succeeded when the model's final action never ran; the truncation should
    surface as an error.
  • No persisted message may be undecodable — a partial tool input must never make the whole session unreadable.
  • Clients should reconcile: when the server reports the session idle, a stale in-flight tool must not leave the
    client stuck ("writing", ESC inert).

Actual behaviour

  • Tool part: state.status: "streaming", executed: false, no time.completed, and
    state.input = "{\"path\": \"/…/workspace/<project>/docs/temp/briefs-s5.md\"" — a string, truncated.
  • Message: finish: "length", tokens: {input: 48052, output: 19000}; session row idle_outcome: "succeeded"
    with time_idle set.
  • GET /api/session/<id>/message → HTTP 500; server log:
    level=ERROR message="failed to decode session message" ref=err_cfe2b809 sessionID=… messageID=…
  • Client: session stuck as "writing", ESC no-op, cannot interact. Meanwhile
    POST /api/session/<id>/interrupt → {"interrupted": false} (200) and the session is not listed by
    GET /api/session/active — the server already considers the session finished.

Workaround used (and what it shows)

Rewriting the tool part in ~/.local/share/opencode/opencode.db (session_message.data, content[2]) to
{"status":"error","input":{…},"error":{…},"time":{…,"completed":…}} makes the server serve the session again
(200). But the client stays stuck on its in-memory pending state until it is restarted — so two independent
layers are involved: the persisted undecodable message, and the client never reconciling.

Screenshot and/or share link

Screenshots available (session rendering, tool shown as failed after the workaround). No share link.

Operating System

Ubuntu 24.04 — Linux 7.0.0-34-generic, x86_64

Terminal

TERM=xterm-256color, COLORTERM=truecolor, TERM_PROGRAM unset, shell /bin/bash

Activity

  1. github-actions commented on Oct 1, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    • #36766: V2 report of truncated OpenAI tool arguments that leave the tool unexecuted.
    • #36421: malformed streamed tool JSON leaves a started V2 tool unresolved.
    • #52490: custom OpenAI-compatible output truncation with finish: "length" incorrectly ending as a successful idle session.
    • #18108: truncated tool calls at the output limit becoming unrecoverable.

    For keybind-related issues, please also check our pinned keybinds documentation: #4997

  2. rekram1-node commented on Oct 8, 2026

    @rekram1-node
    Collaborator

    Automated update

    Fixed by #53685, now merged into v2.

    When a provider ends a response with unfinished local tool input (including finish_reason: length), the runner now settles that call as an unexecuted tool error instead of leaving it permanently streaming. The existing failure projection records a completion timestamp and replaces the partial string input with an object, so the unfinished call no longer strands the session or leaves that terminal tool state undecodable. The error tool result also allows the existing model loop to request a corrected call.

    The merged change includes regression coverage for unfinished tool input after an output limit. Additional follow-up assertions were checked locally for message-list readability and durable-event replay; those extra assertions are not part of the merged PR.

    This resolves the reported failure path for new occurrences in builds containing #53685. It does not retroactively repair already-corrupted sessions, add independent client-state reconciliation, or make every output-limit response a session-level failure. Those are separate concerns.

    Closing as fixed.

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

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