{"record":{"id":"e025e7f8fe1ecfe4","repo":"Billionmail/BillionMail","slug":"claude-returned-no-choices","errorCode":null,"errorMessage":"claude returned no choices","messagePattern":"claude returned no choices","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/video_gen/script.go","lineNumber":164,"sourceCode":"\n// GenerateScript calls Claude API to generate a personalized video script.\nfunc GenerateScript(ctx context.Context, cfg ScriptConfig, input ScriptInput) (*ScriptOutput, error) {\n\tconfig := openai.DefaultConfig(cfg.APIKey)\n\tconfig.BaseURL = cfg.scriptBaseURL()\n\tclient := openai.NewClientWithConfig(config)\n\n\tresp, err := client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{\n\t\tModel:               cfg.Model,\n\t\tMessages:            BuildScriptMessages(input),\n\t\tMaxCompletionTokens: maxScriptTokens,\n\t\tTemperature:         0.7,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"claude script generation: %w\", err)\n\t}\n\n\tif len(resp.Choices) == 0 {\n\t\treturn nil, fmt.Errorf(\"claude returned no choices\")\n\t}\n\n\tscript := strings.TrimSpace(resp.Choices[0].Message.Content)\n\treturn &ScriptOutput{\n\t\tScript:   script,\n\t\tDuration: EstimateDuration(script),\n\t}, nil\n}\n","sourceCodeStart":146,"sourceCodeEnd":173,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/video_gen/script.go#L146-L173","documentation":"After a successful API call, GenerateScript checks that the response contains at least one choice. If Claude returned an empty choices array (or nil), it returns this sentinel error because there is no script text to use.","triggerScenarios":"The provider responds 200 but with zero choices — usually when the request was filtered/blocked by content moderation, or a provider schema change renamed/moved the choices field so parsing yields an empty slice.","commonSituations":"Prompt or prospect content triggering content filters; response fields silently dropped after an SDK/API version upgrade; max completion tokens set so low the response was pruned to nothing.","solutions":["Log the raw response body to see whether choices exist but failed to parse","Upgrade/align the API client SDK version with the provider API version being called","Adjust the prompt to avoid content-filter triggers; test with a benign input","Raise MaxCompletionTokens if truncation is emptying the response"],"exampleFix":"// before\nif len(resp.Choices) == 0 {\n\treturn nil, fmt.Errorf(\"claude returned no choices\")\n}\n// after\nif len(resp.Choices) == 0 {\n\treturn nil, fmt.Errorf(\"claude returned no choices (raw: %.200s)\", rawBody)\n}","handlingStrategy":"try-catch","validationCode":"if input.ProspectText != \"\" && containsBlockedTerms(input.ProspectText) {\n\treturn fmt.Errorf(\"input likely to be rejected by content filter\")\n}","typeGuard":"func scriptValidated(out *ScriptOutput) bool {\n\treturn out != nil && len(strings.TrimSpace(out.Script)) > 0\n}","tryCatchPattern":"out, err := GenerateScript(ctx, cfg, input)\nif err != nil && strings.Contains(err.Error(), \"no choices\") {\n\treturn generateWithFallbackPrompt(ctx, cfg, input) // retry with adjusted prompt\n}","preventionTips":["Log raw response bodies on empty-choices failures to distinguish filtering from parsing bugs","Keep the API client SDK in sync with the provider API version","Sanitize prospect-derived prompt content to reduce moderation rejections","Set MaxCompletionTokens generously enough for a full script"],"tags":["llm","empty-response","content-filter","api-contract","claude"],"backgroundTag":"unexpected-empty-response","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}