Billionmail/BillionMail · error

voice clone: %w

Error message

voice clone: %w

What it means

resolveVoiceID wraps the error from withRetry(CloneVoice(...)) — the Cartesia voice-cloning API call for VOICE_SAMPLE_URL failed after all retries (3 attempts with 5s/15s/45s backoff). This is an external-API failure: the retry layer has already exhausted attempts, so the underlying error reflects persistent failure of the clone request, not a transient blip. The job fails at the voice phase and no voice ID is available for TTS.

Source

Thrown at core/internal/service/video_gen/orchestrator.go:387

		if part != "" {
			m[part] = true
		}
	}
	return m
}

// resolveVoiceID determines which Cartesia voice ID to use.
// If VOICE_SAMPLE_URL is set, clones a voice. Otherwise uses VOICE_DEFAULT_ID.
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)
		}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Read the wrapped error for the HTTP status/code and check CARTESIA_API_KEY is set and valid for the environment
  2. Verify VOICE_SAMPLE_URL is publicly fetchable and returns a supported audio format (curl -I the URL)
  3. Confirm the voice sample meets Cartesia's duration/format requirements and re-encode if needed
  4. Set VOICE_DEFAULT_ID as a fallback so the pipeline uses a pre-cloned voice when cloning fails
  5. Check Cartesia service status/quota; rerun the job once the API is healthy

Example fix

// before
voiceID, err := resolveVoiceID(ctx, voiceCfg)
if err != nil {
    markFailed(ctx, job.ID, "voice", err)
    return err
}
// after
voiceID, err := resolveVoiceID(ctx, voiceCfg)
if err != nil {
    if fallback := os.Getenv("VOICE_DEFAULT_ID"); fallback != "" {
        g.Log().Warningf(ctx, "voice clone failed (%v), falling back to default voice", err)
        voiceID = fallback
    } else {
        markFailed(ctx, job.ID, "voice", err)
        return err
    }
}
Defensive patterns

Strategy: fallback

Validate before calling

if os.Getenv("CARTESIA_API_KEY") == "" {
    return fmt.Errorf("voice phase skipped: CARTESIA_API_KEY not configured")
}
resp, err := http.Head(os.Getenv("VOICE_SAMPLE_URL"))
if err != nil || resp.StatusCode != 200 {
    return fmt.Errorf("VOICE_SAMPLE_URL not reachable: status=%v err=%v", statusOf(resp), err)
}

Try / catch

voiceID, err := resolveVoiceID(ctx, voiceCfg)
if err != nil {
    var apiErr *APIError
    if errors.As(err, &apiErr) {
        g.Log().Errorf(ctx, "cartesia clone failed status=%d body=%s", apiErr.StatusCode, apiErr.Body)
    }
    // fall back to default voice or fail with phase context
    if def := os.Getenv("VOICE_DEFAULT_ID"); def != "" {
        voiceID = def
    } else {
        return err
    }
}

Prevention

When it happens

Trigger: CloneVoice(ctx, cfg, "sender-voice", sampleURL) fails on every attempt: CARTESIA_API_KEY missing/invalid (401/403), VOICE_SAMPLE_URL unreachable or not an audio file (400/422), sample too long/short for Cartesia, rate limits beyond the 5 rps limiter, or network egress blocked.

Common situations: VOICE_SAMPLE_URL pointing at a private/404 URL or an HTML error page instead of a WAV/MP3; expired or wrong-env Cartesia API key; server without outbound internet access; sample audio in an unsupported codec; Cartesia outage or plan quota exhausted.

Related errors


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