{"record":{"id":"bde9b2a05a8df887","repo":"wavetermdev/waveterm","slug":"sse-handler-is-nil","errorCode":null,"errorMessage":"sse handler is nil","messagePattern":"sse handler is nil","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/aiusechat/anthropic/anthropic-backend.go","lineNumber":427,"sourceCode":"\t\treturn sanitizeHostnameInError(fmt.Errorf(\"anthropic %s: %s\", resp.Status, proxyErr.Error))\n\t}\n\n\t// Fall back to truncated raw response\n\tmsg := utilfn.TruncateString(strings.TrimSpace(string(slurp)), 120)\n\tif msg == \"\" {\n\t\tmsg = \"unknown error\"\n\t}\n\treturn sanitizeHostnameInError(fmt.Errorf(\"anthropic %s: %s\", resp.Status, msg))\n}\n\nfunc RunAnthropicChatStep(\n\tctx context.Context,\n\tsse *sse.SSEHandlerCh,\n\tchatOpts uctypes.WaveChatOpts,\n\tcont *uctypes.WaveContinueResponse,\n) (*uctypes.WaveStopReason, *anthropicChatMessage, *uctypes.RateLimitInfo, error) {\n\tif sse == nil {\n\t\treturn nil, nil, nil, errors.New(\"sse handler is nil\")\n\t}\n\n\t// Get chat from store\n\tchat := chatstore.DefaultChatStore.Get(chatOpts.ChatId)\n\tif chat == nil {\n\t\treturn nil, nil, nil, fmt.Errorf(\"chat not found: %s\", chatOpts.ChatId)\n\t}\n\n\t// Validate that chatOpts.Config match the chat's stored configuration\n\tif chat.APIType != chatOpts.Config.APIType {\n\t\treturn nil, nil, nil, fmt.Errorf(\"API type mismatch: chat has %s, chatOpts has %s\", chat.APIType, chatOpts.Config.APIType)\n\t}\n\tif !uctypes.AreModelsCompatible(chat.APIType, chat.Model, chatOpts.Config.Model) {\n\t\treturn nil, nil, nil, fmt.Errorf(\"model mismatch: chat has %s, chatOpts has %s\", chat.Model, chatOpts.Config.Model)\n\t}\n\tif chat.APIVersion != chatOpts.Config.APIVersion {\n\t\treturn nil, nil, nil, fmt.Errorf(\"API version mismatch: chat has %s, chatOpts has %s\", chat.APIVersion, chatOpts.Config.APIVersion)\n\t}","sourceCodeStart":409,"sourceCodeEnd":445,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/aiusechat/anthropic/anthropic-backend.go#L409-L445","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Create and pass an SSE handler: sse := sse.NewSSEHandlerCh(...) before calling RunAnthropicChatStep.","If you do not need streamed output, use the appropriate non-streaming entry point instead of passing nil.","Add a caller-side nil check/log to catch the misconfigured call site early."],"exampleFix":"// before\nvar sse *sse.SSEHandlerCh // nil\nstop, msg, rl, err := RunAnthropicChatStep(ctx, sse, chatOpts, cont)\n\n// after\nsse := sse.NewSSEHandlerCh(bufSize)\nstop, msg, rl, err := RunAnthropicChatStep(ctx, sse, chatOpts, cont)","handlingStrategy":"validation","validationCode":"if sse == nil {\n    return fmt.Errorf(\"cannot run anthropic chat step: SSE handler not initialized\")\n}","typeGuard":"func hasSSEHandler(sse *sse.SSEHandlerCh) bool { return sse != nil }","tryCatchPattern":"stop, msg, rl, err := RunAnthropicChatStep(ctx, sse, chatOpts, cont)\nif err != nil {\n    if err.Error() == \"sse handler is nil\" {\n        // fix call site: initialize the handler before retrying\n    }\n    return err\n}","preventionTips":["Always construct the SSE handler immediately before the chat-step call in the same function.","Never pass a possibly-nil handler variable; use a constructor that cannot return nil.","Add a unit test covering the chat step call path."],"tags":["streaming","nil-pointer","api-contract"],"backgroundTag":"nil-stream-handler","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}