Skip to content

app: dropped provider stream leaves a phantom failed-tool card with the raw fetch error #50586

Open

Description

@jinhuang712

Description

A provider stream dropped mid-tool-call. The call never ran, but the app shows it as a failed tool
with the raw transport error, and the card survives the automatic retry.

Timeline of the turn (v2.0.12, workspace/proxied provider): assistant text streams, then a shell
call starts streaming; the provider socket closes before the arguments finish; opencode records
retry.attempt = 2 and re-runs the step; the retry issues the same command and completes with
exit 0.

The persisted tool part for the failed attempt:

{"type":"tool","name":"shell","executed":false,"state":{"status":"error","input":{},"error":{"type":"provider.transport","message":"ECONNRESET: The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()","status":200}},"time":{"created":1790068832573,"completed":1790068919983}}

state.input is empty and time.ran is absent, so the tool never executed. The app renders its
tool error card (title Shell, summary ECONNRESET) with that raw Bun hint as the detail.

Expected: a call that never ran should not be presented as a failed tool (a neutral "not run"
state at most), and runtime-internal copy should not reach users. Once the retry succeeds the
turn's message-level error is not rendered either, so this card is the only remaining trace and
reads like an unresolved shell failure.

Redacted session JSONL — failed attempt + successful retry (IDs, paths, provider/model redacted)
{"time":{"created":1790068813366,"streamed":1790068919976,"completed":1790068920159},"model":{"id":"<model>","providerID":"<workspace-provider>","variant":"max"},"content":[{"type":"reasoning","text":"<reasoning omitted>","state":{"signature":""},"time":{"created":1790068818210,"completed":1790068822920}},{"type":"text","text":"编译 + 跑相关单测,看有没有测试绑定了老分派行为:"},{"type":"tool","id":"<tool-call-id>","name":"shell","executed":false,"state":{"status":"error","input":{},"error":{"type":"provider.transport","message":"ECONNRESET: The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()","status":200}},"time":{"created":1790068832573,"completed":1790068919983}}],"finish":"error","error":{"type":"provider.transport","message":"ECONNRESET: The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()`","status":200},"retry":{"attempt":2,"at":1790068922241,"error":{"type":"provider.transport","message":"ECONNRESET: The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()`","status":200}}}
{"time":{"created":1790068922399,"streamed":1790068930393,"completed":1790068939758},"model":{"id":"<model>","providerID":"<workspace-provider>","variant":"max"},"content":[{"type":"reasoning","text":"<reasoning omitted>","state":{"signature":""},"time":{"created":1790068925283,"completed":1790068930067}},{"type":"tool","id":"<tool-call-id>","name":"shell","executed":false,"state":{"status":"completed","input":{"command":"cd <repo> && go build ./... 2>&1 | head -20","timeout":180000},"content":[{"type":"text","text":"(no output)"},{"type":"text","text":"Command exited with code 0."}],"metadata":{"status":"completed","truncated":false,"exit":0}},"time":{"created":1790068930072,"ran":1790068930388,"completed":1790068939662}}],"finish":"tool-calls","rawFinish":"tool_use","cost":0.08515237800000001,"tokens":{"input":406,"output":278,"reasoning":0,"cache":{"read":304128,"write":0}}}

Plugins

One local dev plugin (unrelated to this code path).

OpenCode version

2.0.12

Steps to reproduce

  1. Prompt a tool-capable model through a provider/proxy that can drop the socket mid-stream.
  2. Close the connection after tool-input-start and before the call's arguments complete.
  3. Let opencode retry the step. The retry succeeds, but the failed attempt's tool card remains.

Screenshot and/or share link

Screenshot of the red Shell ECONNRESET row to be attached in a comment.

Operating System

macOS (Darwin 25.4.0, arm64)

Terminal

n/a — app UI

Related: #50062, #44254, #36766, #50214.

Activity

  1. jinhuang712 commented on Sep 22, 2026

    @jinhuang712
    Author
    Image
  2. opencodejr commented on Oct 28, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report and the redacted JSONL, they made this easy to follow.

    What you're asking for: when the provider stream drops before a tool call's arguments are complete, the call never ran, so it shouldn't show as a failed tool with the raw transport error. At most it should be a neutral "not run" state, and it shouldn't remain as an unresolved-looking Shell ECONNRESET card after the automatic retry succeeds.

    The app currently does this on purpose. When a step's provider stream fails, every tool call that hasn't run yet is marked as an error with that step's provider error, and a retry is added as a new attempt without changing the earlier one. So this is a change to how failed attempts are shown, not a bug. I'm treating it as an enhancement request; maintainers will consider it, but there's no commitment yet. A plugin can't do this today, because plugins can't change how the app renders tool cards.

    Related issues, in case they're useful:

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