wavetermdev/waveterm · error
sse handler is nil
Error message
sse handler is nil
What it means
RunOpenAIChatStep streams OpenAI responses through an sse.SSEHandlerCh and rejects a nil handler with 'sse handler is nil' at openai-backend.go:470. The handler is the sole delivery mechanism for streamed chunks, so the library fails fast rather than silently dropping output. Like the Gemini backend, this guards against caller initialization mistakes.
Source
Thrown at pkg/aiusechat/openai/openai-backend.go:470
}
if chatMsg.FunctionCall != nil && chatMsg.FunctionCall.CallId == callId {
chatstore.DefaultChatStore.RemoveMessage(chatId, chatMsg.MessageId)
return nil
}
}
return nil
}
func RunOpenAIChatStep(
ctx context.Context,
sse *sse.SSEHandlerCh,
chatOpts uctypes.WaveChatOpts,
cont *uctypes.WaveContinueResponse,
) (*uctypes.WaveStopReason, []*OpenAIChatMessage, *uctypes.RateLimitInfo, error) {
if sse == nil {
return nil, nil, nil, errors.New("sse handler is nil")
}
// Get chat from store
chat := chatstore.DefaultChatStore.Get(chatOpts.ChatId)
if chat == nil {
return nil, nil, nil, fmt.Errorf("chat not found: %s", chatOpts.ChatId)
}
// Validate that chatOpts.Config match the chat's stored configuration
if chat.APIType != chatOpts.Config.APIType {
return nil, nil, nil, fmt.Errorf("API type mismatch: chat has %s, chatOpts has %s", chat.APIType, chatOpts.Config.APIType)
}
if !uctypes.AreModelsCompatible(chat.APIType, chat.Model, chatOpts.Config.Model) {
return nil, nil, nil, fmt.Errorf("model mismatch: chat has %s, chatOpts has %s", chat.Model, chatOpts.Config.Model)
}
if chat.APIVersion != chatOpts.Config.APIVersion {
return nil, nil, nil, fmt.Errorf("API version mismatch: chat has %s, chatOpts has %s", chat.APIVersion, chatOpts.Config.APIVersion)
}View on GitHub (pinned to a4447c1563)
Solutions
- Construct the SSE handler before the call: sseHandler := sse.NewSSEHandlerCh(ctx)
- Check any function that supplies the handler for a nil return and handle it before starting the chat step
- Ensure the handler is created per-request rather than shared/closed across runs
- Add a unit test asserting the handler is non-nil before RunOpenAIChatStep is invoked
Example fix
// before
if handler == nil { /* nothing */ }
go RunOpenAIChatStep(ctx, handler, chatOpts, cont)
// after
if handler == nil {
handler = sse.NewSSEHandlerCh(ctx)
}
go RunOpenAIChatStep(ctx, handler, chatOpts, cont) Defensive patterns
Strategy: type-guard
Validate before calling
if sse == nil {
sse = sse.NewSSEHandlerCh(ctx)
} Type guard
func hasSSEHandler(h *sse.SSEHandlerCh) bool { return h != nil } Try / catch
if err != nil && err.Error() == "sse handler is nil" {
log.Error().Msg("OpenAI chat step invoked with nil SSE handler; check initialization order")
return err
} Prevention
- Construct the SSE handler before starting the chat step, not inside a later goroutine
- Check any factory that returns a handler for nil before passing it on
- Cover the handler-creation path with a unit test so refactors cannot silently break it
When it happens
Trigger: RunOpenAIChatStep is invoked with sse == nil: the handler was never constructed by the caller, a nil pointer propagated from an earlier failure, or the handler was consumed/closed before this call in a reuse scenario.
Common situations: Custom integrations calling RunOpenAIChatStep directly; refactors that moved handler creation; nil returned from a factory function and passed through unchecked; concurrent use where one goroutine nils the shared handler.
Related errors
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/71785a55114ad10a.
Report an issue: GitHub.