wavetermdev/waveterm · error

sse handler is nil

Error message

sse handler is nil

What it means

RunAnthropicChatStep receives a *sse.SSEHandlerCh used to stream Anthropic responses back to the caller. If the handler pointer is nil there is nowhere to deliver streaming events, so the function aborts immediately before any API call. This is a caller-contract violation guard, not an API-side failure.

Source

Thrown at pkg/aiusechat/anthropic/anthropic-backend.go:427

		return sanitizeHostnameInError(fmt.Errorf("anthropic %s: %s", resp.Status, proxyErr.Error))
	}

	// Fall back to truncated raw response
	msg := utilfn.TruncateString(strings.TrimSpace(string(slurp)), 120)
	if msg == "" {
		msg = "unknown error"
	}
	return sanitizeHostnameInError(fmt.Errorf("anthropic %s: %s", resp.Status, msg))
}

func RunAnthropicChatStep(
	ctx context.Context,
	sse *sse.SSEHandlerCh,
	chatOpts uctypes.WaveChatOpts,
	cont *uctypes.WaveContinueResponse,
) (*uctypes.WaveStopReason, *anthropicChatMessage, *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. Create and pass an SSE handler: sse := sse.NewSSEHandlerCh(...) before calling RunAnthropicChatStep.
  2. If you do not need streamed output, use the appropriate non-streaming entry point instead of passing nil.
  3. Add a caller-side nil check/log to catch the misconfigured call site early.

Example fix

// before
var sse *sse.SSEHandlerCh // nil
stop, msg, rl, err := RunAnthropicChatStep(ctx, sse, chatOpts, cont)

// after
sse := sse.NewSSEHandlerCh(bufSize)
stop, msg, rl, err := RunAnthropicChatStep(ctx, sse, chatOpts, cont)
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

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

Try / catch

stop, msg, rl, err := RunAnthropicChatStep(ctx, sse, chatOpts, cont)
if err != nil {
    if err.Error() == "sse handler is nil" {
        // fix call site: initialize the handler before retrying
    }
    return err
}

Prevention

When it happens

Trigger: Calling RunAnthropicChatStep (directly or via the anthropic backend chat path) passing sse = nil, e.g. constructing WaveChatOpts without initializing an SSEHandlerCh, or reusing a code path that skips stream setup for non-streaming calls.

Common situations: Refactored call sites that dropped the SSE handler argument; tests invoking the chat step without stream plumbing; wrappers that only sometimes allocate the handler depending on a 'stream' flag defaulting to false.

Related errors


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