Billionmail/BillionMail · error
claude returned no choices
Error message
claude returned no choices
What it means
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.
Source
Thrown at core/internal/service/video_gen/script.go:164
// GenerateScript calls Claude API to generate a personalized video script.
func GenerateScript(ctx context.Context, cfg ScriptConfig, input ScriptInput) (*ScriptOutput, error) {
config := openai.DefaultConfig(cfg.APIKey)
config.BaseURL = cfg.scriptBaseURL()
client := openai.NewClientWithConfig(config)
resp, err := client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: cfg.Model,
Messages: BuildScriptMessages(input),
MaxCompletionTokens: maxScriptTokens,
Temperature: 0.7,
})
if err != nil {
return nil, fmt.Errorf("claude script generation: %w", err)
}
if len(resp.Choices) == 0 {
return nil, fmt.Errorf("claude returned no choices")
}
script := strings.TrimSpace(resp.Choices[0].Message.Content)
return &ScriptOutput{
Script: script,
Duration: EstimateDuration(script),
}, nil
}
View on GitHub (pinned to fc36c76c05)
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
Example fix
// before
if len(resp.Choices) == 0 {
return nil, fmt.Errorf("claude returned no choices")
}
// after
if len(resp.Choices) == 0 {
return nil, fmt.Errorf("claude returned no choices (raw: %.200s)", rawBody)
} Defensive patterns
Strategy: try-catch
Validate before calling
if input.ProspectText != "" && containsBlockedTerms(input.ProspectText) {
return fmt.Errorf("input likely to be rejected by content filter")
} Type guard
func scriptValidated(out *ScriptOutput) bool {
return out != nil && len(strings.TrimSpace(out.Script)) > 0
} Try / catch
out, err := GenerateScript(ctx, cfg, input)
if err != nil && strings.Contains(err.Error(), "no choices") {
return generateWithFallbackPrompt(ctx, cfg, input) // retry with adjusted prompt
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- lip sync completed but no video URL
- claude script generation: %w
- failed to unmarshal tool call arguments: %v, %s
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/e025e7f8fe1ecfe4.
Report an issue: GitHub.