charmbracelet/crush · error
failed to subscribe to events: %w
Error message
failed to subscribe to events: %w
What it means
SubscribeEvents wraps an error from c.get when opening the SSE stream GET /workspaces/{id}/events?client_id=... with event-stream headers. Raised before any status check, it means the transport itself failed: the stream could not be opened. The SSE channel is never created in this case and the wrapped cause (dial error, context canceled, TLS failure) is preserved.
Source
Thrown at internal/client/proto.go:124
defer rsp.Body.Close()
if err := checkStatus(rsp); err != nil {
return fmt.Errorf("failed to set current session: %w", err)
}
return nil
}
// SubscribeEvents subscribes to server-sent events for a workspace.
func (c *Client) SubscribeEvents(ctx context.Context, id string) (<-chan any, error) {
events := make(chan any, 100)
q := url.Values{"client_id": []string{c.clientID}}
//nolint:bodyclose
rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/events", id), q, http.Header{
"Accept": []string{"text/event-stream"},
"Cache-Control": []string{"no-cache"},
"Connection": []string{"keep-alive"},
})
if err != nil {
return nil, fmt.Errorf("failed to subscribe to events: %w", err)
}
if err := checkStatus(rsp); err != nil {
rsp.Body.Close()
return nil, fmt.Errorf("failed to subscribe to events: %w", err)
}
go func() {
defer rsp.Body.Close()
defer close(events)
scr := bufio.NewReader(rsp.Body)
for {
line, err := scr.ReadBytes('\n')
if errors.Is(err, io.EOF) {
break
}
if err != nil {View on GitHub (pinned to 7944b8e522)
Solutions
- Confirm the server is up and /workspaces/{id}/events is reachable (curl with Accept: text/event-stream).
- Check errors.Is(err, context.Canceled) to distinguish deliberate cancellation from real failures.
- Retry SubscribeEvents with backoff; SSE reconnects are expected to be re-established.
- Validate the server URL/port configuration.
Example fix
// before
events, err := client.SubscribeEvents(ctx, id)
if err != nil { return err }
// after: retry stream establishment
var events <-chan any
err := client.SubscribeEventsErr() // wrapper
for i := 0; i < 3; i++ {
events, err = client.SubscribeEvents(ctx, id)
if err == nil { break }
if errors.Is(err, context.Canceled) { return err }
time.Sleep(500 * time.Millisecond << i)
} Defensive patterns
Strategy: retry
Validate before calling
if ctx.Err() != nil { return fmt.Errorf("cannot subscribe: context already done") }
if err := serverReachable(serverURL); err != nil { return err } Try / catch
var events <-chan any
var err error
for attempt := 0; attempt < 5; attempt++ {
events, err = client.SubscribeEvents(ctx, id)
if err == nil { break }
if errors.Is(err, context.Canceled) { return err }
select {
case <-time.After(time.Second << attempt):
case <-ctx.Done():
return ctx.Err()
}
} Prevention
- Start the server before opening SSE subscriptions.
- Always implement reconnect with backoff for SSE.
- Distinguish context cancellation from transport errors.
- Test SSE handshake with curl -N and Accept: text/event-stream.
When it happens
Trigger: Calling client.SubscribeEvents(ctx, id) when the server is unreachable, the connection is refused/reset during handshake, or ctx is canceled before the request completes. Called by runNonInteractive and tests like TestSubscribeEventsContextCancelClosesEvents.
Common situations: runNonInteractive starting against a dead server; context canceled during startup (e.g. ctrl-c before the stream opens); wrong port/URL; proxy stripping long-lived connections.
Related errors
- empty providers list from catwalk
- lost connection to the crush server
- failed to make request: %w
- failed to download from URL: %w
- failed to write file: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/a2b3494caa3e25c1.
Report an issue: GitHub.