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
- Do not panic in SSE handlers — return errors and let the middleware handle stream teardown.
- If you call code that may panic, wrap it in your own recover and convert to an error.
- Inspect the recovered value/stack (logged via OnClose) to find and fix the root cause.
- 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
- Return errors from handlers instead of panicking.
- Guard against nil maps/slices and unchecked type assertions inside the handler.
- Add unit tests covering nil/empty inputs.
- Log OnClose errors to catch silent panics in production.
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
- client panic
- runtime.Goexit() called in handler or server panic
- sse: Handler must not be nil
- add: invalid http method
- client cannot be nil
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)