gofiber/fiber · error
sse: write retry
Error message
sse: write retry: %w
What it means
Thrown by Stream.Retry when writing the 'retry: <ms>\n\n' frame to the writer fails. Retry is typically called once at stream start (when cfg.Retry > 0) to tell the client how long to wait before reconnecting. A write failure here means the connection was already broken when the stream tried to establish the retry hint.
Solutions
- Treat a retry-write failure as an already-dead stream; return immediately (the middleware sets streamErr and runs OnClose).
- Don't treat this as transient or retry manually — the stream is closed by failLocked.
- Verify the reverse proxy allows long-lived SSE connections.
- Consider setting Retry only when you know the client supports reconnection semantics.
Example fix
// before
if err := stream.Retry(3*time.Second); err != nil { log.Error(err); /* continue writing */ }
// after
if err := stream.Retry(3*time.Second); err != nil {
return // stream is closed, further writes will fail with errStreamClosed
} Defensive patterns
Strategy: try-catch
Validate before calling
// Not locally preventable (transport), but only set Retry when the client needs it.
if clientSupportsReconnect {
cfg.Retry = 3 * time.Second
} Try / catch
if err := stream.Retry(cfg.Retry); err != nil {
return // connection already dead; stream is closed
} Prevention
- Treat a Retry-write error as terminal — failLocked closes the stream.
- Confirm the reverse proxy permits long-lived SSE before setting Retry.
- Don't retry Retry(); the stream's isClosed flag will reject subsequent writes.
When it happens
Trigger: The client disconnected or a proxy reset the connection in the narrow window between stream creation and the initial retry-field write; the underlying connection was never successfully established.
Common situations: Client that opens then immediately closes the EventSource; intermediary rejecting the stream; races where the connection drops during response setup.
Related errors
AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11).
Data as JSON: /api/errors/07472e858d460b07.
Report an issue: GitHub.
Appendix: source
Thrown at middleware/sse/sse.go:161
return s.write(func(w *bufio.Writer) error {
return writeComment(w, comment)
})
}
// Retry writes an SSE retry field and flushes it to the client.
func (s *Stream) Retry(retry time.Duration) error {
if retry <= 0 {
return nil
}
return s.write(func(w *bufio.Writer) error {
// Assemble the whole field in a fixed-size scratch and issue one
// write: no fmt boxing, and a single error branch.
var scratch [32]byte
frame := append(scratch[:0], "retry: "...)
frame = utils.AppendInt(frame, retry.Milliseconds())
frame = append(frame, '\n', '\n')
if _, err := w.Write(frame); err != nil {
return fmt.Errorf("sse: write retry: %w", err)
}
return nil
})
}
func (s *Stream) write(fn func(w *bufio.Writer) error) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.err != nil {
return s.err
}
if s.isClosed {
return errStreamClosed
}
if err := fn(s.w); err != nil {
return s.failLocked(err)
}View on GitHub (pinned to a105acad6c)