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

  1. Log the raw response body to see whether choices exist but failed to parse
  2. Upgrade/align the API client SDK version with the provider API version being called
  3. Adjust the prompt to avoid content-filter triggers; test with a benign input
  4. 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

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


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/e025e7f8fe1ecfe4. Report an issue: GitHub.