Billionmail/BillionMail · error
no voice configured: set VOICE_SAMPLE_URL or VOICE_DEFAULT_I
Error message
no voice configured: set VOICE_SAMPLE_URL or VOICE_DEFAULT_ID
What it means
resolveVoiceID resolves which voice to use for video generation. When neither a per-prospect sample URL nor the VOICE_DEFAULT_ID environment variable provides a voice ID, it returns this error instead of proceeding, because lip-sync and TTS stages cannot run without a voice.
Source
Thrown at core/internal/service/video_gen/orchestrator.go:396
func resolveVoiceID(ctx context.Context, cfg VoiceConfig) (string, error) {
sampleURL := os.Getenv("VOICE_SAMPLE_URL")
defaultID := os.Getenv("VOICE_DEFAULT_ID")
if sampleURL != "" {
resp, err := withRetry(func() (*VoiceCloneResponse, error) {
return CloneVoice(ctx, cfg, "sender-voice", sampleURL)
})
if err != nil {
return "", fmt.Errorf("voice clone: %w", err)
}
return resp.ID, nil
}
if defaultID != "" {
return defaultID, nil
}
return "", fmt.Errorf("no voice configured: set VOICE_SAMPLE_URL or VOICE_DEFAULT_ID")
}
// pollLipSync polls lip sync status until completed or timeout.
func pollLipSync(ctx context.Context, cfg LipSyncConfig, jobID string) (string, error) {
deadline := time.Now().Add(lipSyncTimeout)
for {
if time.Now().After(deadline) {
return "", fmt.Errorf("lip sync timed out after %v", lipSyncTimeout)
}
resp, err := CheckLipSyncStatus(ctx, cfg, jobID)
if err != nil {
return "", fmt.Errorf("check lip sync status: %w", err)
}
switch resp.Status {
case "completed":
if resp.VideoURL == "" {View on GitHub (pinned to fc36c76c05)
Solutions
- Set the VOICE_DEFAULT_ID environment variable to a valid voice ID from the TTS/lip-sync provider
- Or provide a VoiceSampleURL on the pipeline input/prospect so a cloned voice can be created
- Verify the env file is actually loaded into the container/process (docker compose env, systemd EnvironmentFile)
- Add startup validation that fails fast when neither var is set
Example fix
// before
id, err := resolveVoiceID(ctx, input)
// after
if input.VoiceSampleURL == "" && os.Getenv("VOICE_DEFAULT_ID") == "" {
log.Fatal("voice not configured: set VOICE_SAMPLE_URL or VOICE_DEFAULT_ID before starting")
}
id, err := resolveVoiceID(ctx, input) Defensive patterns
Strategy: validation
Validate before calling
if input.VoiceSampleURL == "" && os.Getenv("VOICE_DEFAULT_ID") == "" {
return fmt.Errorf("pipeline requires VOICE_SAMPLE_URL or VOICE_DEFAULT_ID")
} Try / catch
id, err := resolveVoiceID(ctx, input)
if err != nil {
return fmt.Errorf("voice resolution: %w", err)
} Prevention
- Set VOICE_DEFAULT_ID in every deployment environment (Docker env, CI, prod)
- Validate required env vars at process startup with fail-fast checks
- Require a voice sample URL in prospect onboarding so records always have a voice
- Document the voice config requirements in the deployment README
When it happens
Trigger: RunPipeline reaches the voice-resolution step while the pipeline input has an empty VoiceSampleURL and the VOICE_DEFAULT_ID environment variable is unset or empty, so the function falls through all candidates and returns the error.
Common situations: Deploying the video-gen service without the voice env vars configured; a prospect record missing a recorded voice sample; an .env file not loaded in Docker; typo'd env var name (VOICE_DEFAULT_ID vs VOICE_DEFAULTID).
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- Failed to get configuration
- configuration file does not exist: %s
- Supplier not found
- Model not found
- failed to initialize AI client
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/17926b3797144fdb.
Report an issue: GitHub.