gofiber/fiber · error

sse: handler panic

Error message

sse: handler panic: %v

What it means

Set as streamErr when the user-provided SSE handler function (cfg.Handler) panics. A deferred recover catches the panic value and converts it into this error, which is then delivered to the OnClose callback as the stream's final error. The %v is the recovered panic value (not wrapped, since panic values are arbitrary types).

Solutions

  1. Do not panic in SSE handlers — return errors and let the middleware handle stream teardown.
  2. If you call code that may panic, wrap it in your own recover and convert to an error.
  3. Inspect the recovered value/stack (logged via OnClose) to find and fix the root cause.
  4. Add table tests for handler edge cases (nil inputs, empty slices).

Example fix

// before
streamErr = cfg.Handler(c, stream) // a panic here is caught, stream ends

// after — make the handler panic-safe at the source
func handler(c fiber.Ctx, s *sse.Stream) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("handler recovered: %v", r)
        }
    }()
    // ... produce events, returning errors instead of panicking
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Wrap risky handler logic so a panic becomes an error you control.
func safeHandler(c fiber.Ctx, s *sse.Stream) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("handler recovered: %v", r)
        }
    }()
    return produceEvents(c, s)
}

Try / catch

// The middleware already recovers; use OnClose to observe the panic-derived error.
cfg := sse.NewWithConfig(sse.Config{
    Handler: myHandler,
    OnClose: func(c fiber.Ctx, err error) {
        if err != nil { log.Printf("stream ended: %v", err) }
    },
})

Prevention

When it happens

Trigger: Any panic inside the handler closure passed to sse.NewWithConfig's Handler — nil pointer dereference, index/slice out of range, type assertion failure, or an explicit panic() call.

Common situations: Bugs in the event-producing handler (nil map writes, unchecked type assertions, out-of-bounds slices); third-party code inside the handler that panics; nil pointer on a context field.

Related errors


AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11). Data as JSON: /api/errors/9ddf10af479275bb. Report an issue: GitHub.

Appendix: source

Thrown at middleware/sse/sse.go:55

		lastEventID := c.Get(fiber.HeaderLastEventID)

		c.Abandon()

		return c.SendStreamWriter(func(w *bufio.Writer) {
			stream := newStream(streamContext, w, lastEventID, c.App().Config().JSONEncoder)
			var streamErr error
			defer func() {
				if cfg.OnClose != nil {
					finalErr := streamErr
					if finalErr == nil {
						finalErr = stream.Err()
					}
					cfg.OnClose(c, finalErr)
				}
			}()
			defer func() {
				if recovered := recover(); recovered != nil {
					streamErr = fmt.Errorf("sse: handler panic: %v", recovered)
				}
			}()
			defer stream.closeStream()

			if cfg.Retry > 0 {
				streamErr = stream.Retry(cfg.Retry)
				if streamErr != nil {
					return
				}
			}

			if !cfg.DisableHeartbeat {
				stopHeartbeat := stream.startHeartbeat(cfg.HeartbeatInterval)
				if stopHeartbeat != nil {
					defer stopHeartbeat()
				}
			}

View on GitHub (pinned to a105acad6c)