charmbracelet/crush · warning · ErrStreamClosed
the event stream closed; reconnecting
Error message
the event stream closed; reconnecting
What it means
ErrStreamClosed means an established event subscription stream ended. Resubscribing usually succeeds immediately, but any events published while the stream was down are lost for good, so the client treats it as a degraded link requiring a resync.
Source
Thrown at internal/workspace/workspace.go:48
// uninitialized agent apart from a lost server connection.
var (
// ErrAgentNotInitialized means the workspace exists but its coder
// agent has not been configured/initialized (e.g. no model set).
ErrAgentNotInitialized = errors.New("coder agent is not initialized")
// ErrServerUnreachable means the client could not reach the server
// to determine the agent's status (server down, or the workspace was
// torn down out from under the client).
ErrServerUnreachable = errors.New("lost connection to the crush server")
// ErrWorkspaceGone means the server is reachable but no longer knows
// this client's workspace: it was torn down, or the server was
// replaced underneath the client. The subscription loop re-registers
// the workspace in the background when it sees this.
ErrWorkspaceGone = errors.New("the server reset this workspace; reconnecting")
// ErrStreamClosed means an established event stream ended.
// Resubscribing usually succeeds immediately, but events published in
// the meantime are lost for good, so the client treats it as a
// degraded link that requires a resync.
ErrStreamClosed = errors.New("the event stream closed; reconnecting")
)
// ConnectionState describes the health of the client-server link as
// reported by the [ClientWorkspace] subscription loop.
type ConnectionState int
const (
// ConnectionDegraded means the event stream is down (or the workspace
// was lost server-side) and the client is retrying or re-registering
// in the background.
ConnectionDegraded ConnectionState = iota
// ConnectionRecovered means the event stream was re-established,
// possibly against a re-created workspace.
ConnectionRecovered
)
// ConnectionEvent is delivered to the TUI as a tea.Msg on degraded and
// recovered transitions of the client-server link. Local (in-process)View on GitHub (pinned to 7944b8e522)
Solutions
- Resubscribe immediately (the loop usually reconnects at once)
- Trigger a state resync after reconnection to recover events lost during the gap
- Enable TCP keepalives or shorter proxy idle timeouts to reduce silent stream drops
Defensive patterns
Strategy: retry
Type guard
func isStreamClosed(err error) bool { return errors.Is(err, workspace.ErrStreamClosed) } Try / catch
if err != nil {
if errors.Is(err, workspace.ErrStreamClosed) {
resubscribe(ctx)
resyncState(ctx) // events published during the gap are lost
return nil
}
return err
} Prevention
- Always resync local state after ErrStreamClosed; gap events are permanently lost
- Configure keepalives/proxy idle timeouts above the stream heartbeat
- Treat this as degraded-link telemetry to detect flaky networks
When it happens
Trigger: runSubscription's established event stream terminates (server-side stream close, transient network interruption) after a successful subscribe.
Common situations: Idle connections reaped by proxies or load balancers; server gracefully closing streams during restart; long-running sessions where the SSE/stream connection times out.
Related errors
- client has been retired
- the server reset this workspace; reconnecting
- failed to start agent processing stream: %w
- agent processing failed: %w
- message content is shorter than read bytes: %d < %d
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/079676706d2c344d.
Report an issue: GitHub.