Skip to content

opencode backend: long/complex prompts end with no content ('produced no stream output') #71

Closed

Description

@drewstone

Summary

opencode backend is unreliable on long / complex prompts: the response ends with no assistant content and the caller (litellm) sees MidStreamFallbackError: OpenAIException - opencode produced no stream output. Short prompts succeed. claude-code and codex on the identical prompts are reliable.

Repro

Running the TaxCalcBench harness sweep (a ~large 1040 tax-return prompt) through the bridge:

model=opencode/zai-coding-plan/glm-5.1
# short prompt ("say OK") -> HTTP 200 in ~4s, fine
# full tax-return prompt  -> "opencode produced no stream output" (fails most attempts)

On a 51-case sweep, opencode/glm-5.1 produced ~1/51 while claude-code/sonnet and codex/gpt-5.5 ran clean to completion.

Diagnosis

  • src/backends/opencode.ts already emits keepalive SSE comments during silent stretches (30da47e), so the socket no longer dies.
  • But on a hard task opencode appears to work silently, emit only keepalives, then exit having produced no gradeable content deltas — which surfaces to the caller as "produced no stream output" (empty stream).
  • So the keepalive fix solved socket-death but not the empty-completion case: we never distinguish "still working" from "exited with nothing," and we don't surface why opencode produced nothing (stderr / non-zero exit / internal error event).

Impact

opencode is effectively unusable for long-running agentic benchmarking via the bridge. Had to drop it from the TaxCalcBench harness comparison (kept claude-code + codex). By contrast the streaming harnesses (claude-code, codex) are reliable because they emit content continuously.

Suggested fixes to consider

  1. When the stream closes with zero content deltas, throw a descriptive BackendError that includes opencode's stderr tail + exit code (like the kimi path at kimi.ts:332), instead of an empty stream that the caller mislabels as "no stream output."
  2. Investigate whether opencode needs a different invocation for long tasks (e.g. --print/non-interactive output mode, or reading its final message from the session rather than the live stream).
  3. Consider a completion-based read (poll opencode's session for the final assistant message) rather than relying purely on the live stdout stream, since opencode batches on complex tasks.

Not the sandbox

The sandbox (@tangle-network/sandbox) opencode adapter has a related but separate symptom: empty_visible_output within 60000ms. Same root behavior (opencode silent on long tasks), different adapter/timeout — worth fixing in both.

Activity

  1. drewstone commented on Jul 17, 2026

    @drewstone
    OwnerAuthor

    Root cause is upstream: anomalyco/opencode#22243

    This is a symptom of opencode#22243 — "opencode run produces no stdout output while running" (open, assigned to @kitlangton, PR #33146 linked). opencode's non-interactive run mode buffers all output until completion — no streaming progress. The upstream reporter proved via the session DB that the agent is making progress (Glob/Read steps ~10s each) while stdout stays empty.

    That fully explains our symptom: the keepalive keeps the socket alive, but opencode emits no content deltas until the very end (or exits with nothing on a hard task), so litellm sees an empty stream → "produced no stream output." The streaming harnesses (claude-code, codex) don't buffer, which is why they're reliable.

    Concrete workaround this reveals for the bridge

    The upstream issue shows the result/progress is in opencode's session DB even when stdout is empty. So the durable fix for our backend is a completion-based read: after the run, read the final assistant message from opencode's session (we already capture internal_session_id) rather than relying purely on the live stdout stream. That sidesteps the upstream buffering entirely.

    Short term: option 1 above (throw with stderr tail + exit code on zero-content close) still worth doing so failures aren't silently mislabeled.

    Tracking upstream: fixed when opencode#22243 / opencode PR #33146 lands (streaming progress in run mode).

  2. drewstone commented on Jul 17, 2026

    @drewstone
    OwnerAuthor

    Correction: the right fix already exists in agent-dev-container — use opencode serve, not opencode run

    Ignore my earlier "read the session DB" suggestion — it's a worse workaround. agent-dev-container's sidecar already solves this properly and cli-bridge should match it.

    • cli-bridge (this bug): spawns opencode run --format json — the one-shot mode that buffers all output (upstream opencode run produces no stdout output while running anomalyco/opencode#22243). On long tasks it emits keepalives then finishes with no content deltas.
    • agent-dev-container sidecar (works): runs opencode serve (persistent HTTP server) and talks to it via the @tangle-network/sdk-provider-opencode SDK over its HTTP/event API. Streaming works; the run-mode buffering bug never applies. See apps/sidecar/src/interactive/interactive-session-manager.ts:74 ("headless integration uses opencode serve") and backends/cli-agent-bindings.ts importing OpencodeProviderAdapter.

    Recommended fix

    Replace the opencode run spawn in src/backends/opencode.ts with opencode serve + the @tangle-network/sdk-provider-opencode client (or a thin HTTP client to the served session), mirroring the sidecar. This makes opencode as reliable as claude-code/codex on long agentic prompts and removes the dependence on upstream #22243 landing.

  3. drewstone commented on Oct 30, 2026

    @drewstone
    OwnerAuthor

    The empty-stream case is now diagnosed and the long-task case streams: a zero-content close names opencode's stderr tail and exit (src/backends/opencode.ts:389), #225 reads the error opencode actually writes and #264 carries its HTTP status, and #228/#266 forward reasoning, tool-call starts and tool outcomes as they happen, so a long agentic turn through opencode run --format json is no longer silent until exit. Bridge-driven opencode cells have been completing >10-minute build phases since August (#105). The opencode serve migration was not needed; the sandbox adapter symptom stays with @tangle-network/sandbox.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions