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

  1. Treat a retry-write failure as an already-dead stream; return immediately (the middleware sets streamErr and runs OnClose).
  2. Don't treat this as transient or retry manually — the stream is closed by failLocked.
  3. Verify the reverse proxy allows long-lived SSE connections.
  4. 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

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)