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

  1. Construct the SSE handler before the call: sseHandler := sse.NewSSEHandlerCh(ctx)
  2. Check any function that supplies the handler for a nil return and handle it before starting the chat step
  3. Ensure the handler is created per-request rather than shared/closed across runs
  4. 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

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.