wavetermdev/waveterm · error

sse handler is nil

Error message

sse handler is nil

What it means

RunGeminiChatStep streams Gemini responses through an SSE handler channel. It rejects a nil *sse.SSEHandlerCh at the top of the function because without a handler there is nowhere to deliver streaming events. This is a programmer-error guard: the caller must construct the SSE handler before starting the chat step.

Source

Thrown at pkg/aiusechat/gemini/gemini-backend.go:187

		return nil, err
	}

	// Set headers
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("x-goog-api-key", opts.APIToken)

	return req, nil
}

// RunGeminiChatStep executes a chat step using the Gemini API
func RunGeminiChatStep(
	ctx context.Context,
	sseHandler *sse.SSEHandlerCh,
	chatOpts uctypes.WaveChatOpts,
	cont *uctypes.WaveContinueResponse,
) (*uctypes.WaveStopReason, *GeminiChatMessage, *uctypes.RateLimitInfo, error) {
	if sseHandler == 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 chat.Model != chatOpts.Config.Model {
		return nil, nil, nil, fmt.Errorf("model mismatch: chat has %s, chatOpts has %s", chat.Model, chatOpts.Config.Model)
	}

	// Context with timeout if provided
	if chatOpts.Config.TimeoutMs > 0 {

View on GitHub (pinned to a4447c1563)

Solutions

  1. Create the SSE handler before calling RunGeminiChatStep: handler := sse.NewSSEHandlerCh(...)
  2. Trace why the handler variable is nil at call time (initialization order, error path, or copy of a nil pointer)
  3. If the handler creation itself failed, abort the chat step instead of proceeding with nil
  4. In tests, provide a real or mocked SSEHandlerCh rather than nil

Example fix

// before
var sseHandler *sse.SSEHandlerCh
go RunGeminiChatStep(ctx, sseHandler, chatOpts, cont)
// after
sseHandler := sse.NewSSEHandlerCh(ctx)
go RunGeminiChatStep(ctx, sseHandler, chatOpts, cont)
Defensive patterns

Strategy: type-guard

Validate before calling

if sseHandler == nil {
    return fmt.Errorf("cannot run Gemini chat step: SSE handler not initialized")
}

Type guard

func handlerReady(h *sse.SSEHandlerCh) bool { return h != nil }

Try / catch

if err != nil && err.Error() == "sse handler is nil" {
    return fmt.Errorf("internal wiring bug: chat step started without an SSE handler")
}

Prevention

When it happens

Trigger: RunGeminiChatStep is called with sseHandler == nil — e.g. a caller forgot to create sse.NewSSEHandlerCh, a refactor reordered initialization so the handler is created after the chat step starts, or a nil handler was passed through from an error path.

Common situations: Wiring a new backend integration and skipping handler setup; concurrency bug where the handler variable is still nil when the goroutine launches; tests calling RunGeminiChatStep directly without a handler stub.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/7cb6e97a6b35c1e3. Report an issue: GitHub.