{"record":{"id":"6c4744f7fe84e0a0","repo":"wavetermdev/waveterm","slug":"chatopts-clientid-is-required","errorCode":null,"errorMessage":"chatOpts.ClientId is required","messagePattern":"chatOpts\\.ClientId is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/aiusechat/anthropic/anthropic-convertmessage.go","lineNumber":37,"sourceCode":"\t\"github.com/google/uuid\"\n\t\"github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore\"\n\t\"github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes\"\n\t\"github.com/wavetermdev/waveterm/pkg/util/logutil\"\n\t\"github.com/wavetermdev/waveterm/pkg/util/utilfn\"\n\t\"github.com/wavetermdev/waveterm/pkg/wavebase\"\n)\n\n// these conversions are based off the anthropic spec\n// and the aiprompts/aisdk-uimessage-type.md doc (v5)\n\n// buildAnthropicHTTPRequest creates a complete HTTP request for the Anthropic API\nfunc buildAnthropicHTTPRequest(ctx context.Context, msgs []anthropicInputMessage, chatOpts uctypes.WaveChatOpts) (*http.Request, error) {\n\topts := chatOpts.Config\n\tif opts.Model == \"\" {\n\t\treturn nil, errors.New(\"ai:model is required\")\n\t}\n\tif chatOpts.ClientId == \"\" {\n\t\treturn nil, errors.New(\"chatOpts.ClientId is required\")\n\t}\n\n\t// Set defaults\n\tendpoint := opts.Endpoint\n\tif endpoint == \"\" {\n\t\treturn nil, errors.New(\"ai:endpoint is required\")\n\t}\n\n\tmaxTokens := opts.MaxTokens\n\tif maxTokens <= 0 {\n\t\tmaxTokens = AnthropicDefaultMaxTokens\n\t}\n\n\t// Convert messages to clear FileName fields from Source blocks\n\tconvertedMsgs := make([]anthropicInputMessage, len(msgs))\n\tfor i, msg := range msgs {\n\t\tconvertedMsgs[i] = convertMessageForAPI(msg)\n\t}","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/aiusechat/anthropic/anthropic-convertmessage.go#L19-L55","documentation":"buildAnthropicHTTPRequest requires chatOpts.ClientId, which identifies the calling client/block for the request. An empty ClientId breaks request construction and possibly downstream authorization/bookkeeping, so the code rejects it before building the HTTP request.","triggerScenarios":"RunAnthropicChatStep invoked with WaveChatOpts whose ClientId field is empty — typically when the caller builds opts manually instead of deriving them from the block context.","commonSituations":"Background/standalone runners calling the AI step without a block context; tests constructing WaveChatOpts literal; a refactor that renamed or stopped populating ClientId upstream.","solutions":["Populate chatOpts.ClientId from the block/context id before invoking the chat step.","Use the standard entry point that assembles WaveChatOpts from block metadata rather than hand-building opts.","Assert ClientId non-empty in the caller with a descriptive error."],"exampleFix":"// before\nopts := uctypes.WaveChatOpts{ChatId: chatId, Config: cfg}\n\n// after\nopts := uctypes.WaveChatOpts{ChatId: chatId, ClientId: blockId, Config: cfg}","handlingStrategy":"validation","validationCode":"if chatOpts.ClientId == \"\" {\n    return fmt.Errorf(\"config error: ClientId (block id) required for anthropic chat\")\n}","typeGuard":"func hasClientId(o uctypes.WaveChatOpts) bool { return o.ClientId != \"\" }","tryCatchPattern":"if err := validateChatOpts(chatOpts); err != nil {\n    return err // fails fast on empty ClientId before the API call\n}","preventionTips":["Derive WaveChatOpts from the block context so ClientId is filled automatically.","Never hand-build WaveChatOpts literals outside a shared constructor.","Log opts at debug level to spot empty ids during development."],"tags":["configuration","validation","api-contract"],"backgroundTag":"missing-required-config","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}