Feature hasn't been suggested before.
Describe the enhancement you want to request
A visible connection-health surface in the web UI: stream status, reconnect progress, and client/server version mismatch.
Today, when the event stream is degraded, the UI gives no signal: the stream either works or the app silently goes stale (#39030 family). The client implementation that was merged in #47571 already tracked a connected | connecting | reconnecting status internally — it was just never surfaced, and the code has since been lost in the v2 refactor. The server already sends server.heartbeat every 10s, so the data needed to drive this UI exists on the wire today.
Proposal
A small, persistent status affordance (e.g. header or sidebar footer) showing:
- Stream state:
connected / reconnecting (attempt n, retrying in Xs) / offline, driven by heartbeat age — e.g. degraded if no event (heartbeat counts) within ~3× the 10s heartbeat interval, and a lastEventAt timestamp.
- Reconnect progress: attempt count, next retry delay, last disconnect reason.
- Version health: the app knows the server version at bootstrap; if the embedded/proxied UI asset version differs from the server's (or the server version changes under an open tab — deploy mid-session), show "client outdated — reload" with a safe reload that preserves the draft prompt.
- Queue feedback: pending/queued prompts while offline, instead of silent loss or apparent hanging.
// state sketch driven by the existing stream
type StreamHealth = {
state: "connected" | "reconnecting" | "offline"
lastEventAt: number // updated on ANY event, incl. server.heartbeat
attempt: number
nextRetryAt?: number
serverVersion?: string // from server.connected or a /health endpoint
clientVersion: string // build-time constant
}
Acceptance ideas
Benefits
Users currently cannot distinguish "session is thinking" from "connection is dead" — which is why stale-UI bug reports keep recurring. An honest status surface turns a whole class of silent failures into observable, self-healing ones.
Related work
| Ref |
State |
What it reports / fixes |
| PR #47571 |
merged (lost) |
Already tracked connected | connecting | reconnecting internally — the status model this UI would surface. |
| #21716 |
closed |
SSE stale after laptop sleep/wake with no detection or recovery — the symptom this surface makes visible. |
| #36582 |
open issue |
TUI: make API failures explicit and actionable — same principle, TUI side. |
| #51291 |
open issue |
Provider stream can disappear while a tool call stays "running" forever — server-side analogue of invisible liveness. |
| #48622 |
open issue |
Background auto-update — pairs with the version-mismatch affordance. |
| #41280 |
open issue |
Concrete instance of bundled UI version mismatch this surface would catch. |
| #47563 |
open issue |
Heartbeat floods opencode.log at INFO (95GB/34d reported) — if health UI consumes heartbeats, consider demoting their log level too. |
| #51857 / #51858 |
open issues |
Companion fixes: stall watchdog (live-update staleness) and cache directives (deploy staleness). This surface is what lets users see both working. |
Triage note for maintainers
Recurring stale-UI reports (#39030, #47258, #45860, #21716, #46733) share one property: the failure is invisible. Even after the watchdog lands (#51857), users need to distinguish "thinking" from "disconnected". The data already exists on the wire (server.connected, 10s server.heartbeat) — this is a client-only feature with no server contract change, so it is cheap to ship alongside the watchdog work.
Feature hasn't been suggested before.
Describe the enhancement you want to request
A visible connection-health surface in the web UI: stream status, reconnect progress, and client/server version mismatch.
Today, when the event stream is degraded, the UI gives no signal: the stream either works or the app silently goes stale (#39030 family). The client implementation that was merged in #47571 already tracked a
connected | connecting | reconnectingstatus internally — it was just never surfaced, and the code has since been lost in the v2 refactor. The server already sendsserver.heartbeatevery 10s, so the data needed to drive this UI exists on the wire today.Proposal
A small, persistent status affordance (e.g. header or sidebar footer) showing:
connected/reconnecting (attempt n, retrying in Xs)/offline, driven by heartbeat age — e.g. degraded if no event (heartbeat counts) within ~3× the 10s heartbeat interval, and alastEventAttimestamp.Acceptance ideas
reconnecting, counts retries, and the session resyncs on recovery without a manual refresh (depends on the stall watchdog — see the regression report in Mobile browser tab does not reconnect SSE stream after returning from another app #39030 / PR fix(client): detect stalled event streams and resync on foreground #47571 context).server.connected,server.heartbeat, and existing version/config endpoints.Benefits
Users currently cannot distinguish "session is thinking" from "connection is dead" — which is why stale-UI bug reports keep recurring. An honest status surface turns a whole class of silent failures into observable, self-healing ones.
Related work
connected | connecting | reconnectinginternally — the status model this UI would surface.opencode.logat INFO (95GB/34d reported) — if health UI consumes heartbeats, consider demoting their log level too.Triage note for maintainers
Recurring stale-UI reports (#39030, #47258, #45860, #21716, #46733) share one property: the failure is invisible. Even after the watchdog lands (#51857), users need to distinguish "thinking" from "disconnected". The data already exists on the wire (
server.connected, 10sserver.heartbeat) — this is a client-only feature with no server contract change, so it is cheap to ship alongside the watchdog work.