Skip to content

Web client: 2s event-stream connect timeout with fixed 1s retry causes minutes-long loading behind slow/buffering proxies #53498

Description

@LeonMueller-OneAndOnly

Description

When the web UI is served by opencode serve, its event stream client gives each /api/event attempt a hard 2s deadline to receive and parse the first server.connected event. After a failure it retries on a fixed 1s delay with no backoff and no attempt limit. Behind a network path that delays the start of streamed responses (likely a corporate proxy or TLS-inspecting AV), the connection works but is slow to start, and the UI stays in its loading state for minutes: the session list shows "loading" and no agents can be selected.

Source: packages/client/src/solid/connection.ts (same code in v2.0.23, v2.0.24 and the current v2 head):

const connectTimeout = 2_000
const reconnectDelay = 1_000

// connect(): the deadline is cleared only after the first parsed event
const timeout = setTimeout(() => request.abort(new Error("Timed out connecting to server")), connectTimeout)
const first = await iterator.next()
if (first.value.type !== "server.connected") return { error: new Error("Event stream did not start with server.connected"), ... }
clearTimeout(timeout)

// runStream(): after every failure
await wait(reconnectDelay, controller.signal)   // fixed 1s

The web client passes no reconnect resolver, so this fixed loop is the only recovery path. The onActivity/touch() hook already sees incoming bytes, including keepalive comments, but it only feeds the idle watchdog after the connection is established. It does not count toward the connect deadline.

Observed

  • The server sits behind a reverse proxy and sends server.connected immediately. We verified this with curl, and the response headers are content-type: text/event-stream, cache-control: no-cache, no-transform and x-accel-buffering: no. A : heartbeat comment follows every 15s.
  • For one user on a slow, buffering network path, the server log showed a /api/event request every ~3s, each lasting almost exactly 2.0s before being interrupted (InterruptError, status 200). There were about 140 such attempts over about 7 minutes before one succeeded and the UI loaded.
  • Another user on the same host produced more than 3000 of these 2s aborts over several days.

The 2s deadline turns a slow but working network into minutes of loading, and the fixed 1s retry keeps hitting the server the whole time.

Proposed fix

  1. Make the connect deadline more tolerant (e.g. 10–15s) and/or configurable, or count any received bytes (including SSE comments, via the existing onActivity hook) as progress instead of requiring the parsed server.connected event within 2s.
  2. Use exponential backoff with jitter for reconnects (e.g. 1s → 2s → 4s …, capped at ~30s), and reset it after a successful connection.
  3. After N failed attempts, show a visible "cannot establish live connection" state instead of loading indefinitely.
  4. Optional, server side: send an initial padding comment so that proxies which buffer by response size flush the first event.

Related

Plugins

none

OpenCode version

2.0.23 (code unchanged in 2.0.24 and on v2)

Steps to reproduce

  1. Run opencode serve and open the web UI through a network path that holds back the start of streamed responses for more than 2s (e.g. a TLS-inspecting proxy/AV, or simulate with a proxy that holds the first bytes of text/event-stream responses for 3s).
  2. The UI stays on loading. The server log shows /api/event requests every ~3s, each aborted after ~2.0s.

Operating System

Server: Linux. Client: browser behind a corporate network.

Terminal

N/A (web UI)

Activity

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