{"id":"cf0c96761cb3638b","repo":"gofiber/fiber","slug":"sse-write-heartbeat-w","errorCode":null,"errorMessage":"sse: write heartbeat: %w","messagePattern":"sse: write heartbeat: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"middleware/sse/event.go","lineNumber":76,"sourceCode":"\t}\n\tif event.Retry > 0 {\n\t\tappendField(&frame, \"retry\", utils.FormatInt(event.Retry.Milliseconds()))\n\t}\n\tif data.hasData {\n\t\tappendData(&frame, data.data)\n\t}\n\tframe.WriteByte('\\n') //nolint:errcheck // bytes.Buffer writes never fail.\n\tif _, err := w.Write(frame.Bytes()); err != nil {\n\t\treturn fmt.Errorf(\"sse: write event: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc writeComment(w *bufio.Writer, comment string) error {\n\tcomment = sanitizeComment(comment)\n\tif comment == \"\" {\n\t\tif _, err := w.WriteString(\":\\n\\n\"); err != nil {\n\t\t\treturn fmt.Errorf(\"sse: write heartbeat: %w\", err)\n\t\t}\n\t\treturn nil\n\t}\n\tfor line := range strings.SplitSeq(comment, \"\\n\") {\n\t\tif _, err := fmt.Fprintf(w, \": %s\\n\", line); err != nil {\n\t\t\treturn fmt.Errorf(\"sse: write comment: %w\", err)\n\t\t}\n\t}\n\tif _, err := w.WriteString(\"\\n\"); err != nil {\n\t\treturn fmt.Errorf(\"sse: finish comment: %w\", err)\n\t}\n\treturn nil\n}\n\ntype eventPayload struct {\n\tdata    string\n\thasData bool\n}","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/gofiber/fiber/blob/9a4c7e57fe0b080a04235d28a4b0d2b4b353d58c/middleware/sse/event.go#L58-L94","documentation":"The SSE heartbeat (an empty comment ':\\n\\n') could not be written to the response stream. Heartbeats are emitted on a timer by startHeartbeat to keep the connection alive; a failure means the client has disconnected or the connection broke.","triggerScenarios":"The heartbeat ticker fires after the client closed the connection; network drop during an otherwise-idle SSE connection; proxy closed the idle link.","commonSituations":"Idle SSE connections behind strict proxies; clients that sleep/suspend; mobile network changes.","solutions":["Heartbeat write failures already stop the heartbeat goroutine internally; ensure your handler also observes stream.Done() and exits.","Make heartbeat interval shorter than the proxy idle timeout so the link stays open while the client is alive.","Handle stream closure gracefully in the main handler loop."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// choose heartbeat < proxy idle timeout\napp.Use(sse.New(sse.Config{\n    Handler: sseHandler,\n    HeartbeatInterval: 10 * time.Second,\n}))","typeGuard":null,"tryCatchPattern":"// heartbeat failures are handled internally; observe stream end in your handler.\ngo func() {\n    <-stream.Done()\n    cancel() // unblock your producer\n}()","preventionTips":["Set HeartbeatInterval below your reverse-proxy idle timeout.","Don't hold resources after stream.Done() closes."],"tags":["sse","network","heartbeat","fiber"],"analyzedSha":"9a4c7e57fe0b080a04235d28a4b0d2b4b353d58c","analyzedAt":"2026-08-04T21:44:03.395Z","schemaVersion":2}