Hmbown/CodeWhale · error

Model '{model}' requires cloned voice data. Pass --clone-voi

Error message

Model '{model}' requires cloned voice data. Pass --clone-voice <mp3|wav> or --voice <data-uri>.

What it means

Model-specific validation in `synthesize_speech`: when the model id contains `voiceclone`, the request must carry cloned voice data — either a file via `--clone-voice <mp3|wav>` or a data-URI via `--voice <data-uri>`. The client checks `voice.is_none()` and rejects locally before building the request body, since the clone flow cannot proceed without source audio.

Source

Thrown at crates/tui/src/client.rs:2373

        let instruction = request
            .instruction
            .as_deref()
            .map(str::trim)
            .filter(|value| !value.is_empty());
        let voice = request
            .voice
            .as_deref()
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .map(str::to_string);

        if model_lower.contains("voicedesign") && instruction.is_none() {
            anyhow::bail!(
                "Model '{model}' requires a voice design prompt. Pass --voice-prompt or --instruction."
            );
        }
        if model_lower.contains("voiceclone") && voice.is_none() {
            anyhow::bail!(
                "Model '{model}' requires cloned voice data. Pass --clone-voice <mp3|wav> or --voice <data-uri>."
            );
        }

        let mut audio = json!({
            "format": audio_format.clone(),
        });
        if let Some(voice) = voice.as_deref() {
            audio["voice"] = json!(voice);
        }

        let body = build_speech_synthesis_body(&model, &text, instruction, audio);

        let url = api_url(&self.base_url, "chat/completions");
        let response = self.send_json_with_retry(&url, &body).await?;
        let status = response.status();
        if !status.is_success() {
            let raw_error_text = bounded_error_text(response, ERROR_BODY_MAX_BYTES).await;

View on GitHub (pinned to 8880682c63)

Solutions

  1. Provide source audio: `--clone-voice recording.mp3` (or a .wav).
  2. Alternatively pass the voice payload directly as a data-URI with `--voice <data-uri>`.
  3. If you do not have reference audio, use a non-voiceclone model instead.

Example fix

# before
/speech --model mimo-voiceclone-v1 "hello world"
# after
/speech --model mimo-voiceclone-v1 --clone-voice my-voice.mp3 "hello world"
Defensive patterns

Strategy: validation

Validate before calling

fn voiceclone_needs_voice(model: &str, voice: Option<&str>) -> bool {
    model.to_ascii_lowercase().contains("voiceclone")
        && voice.map(str::trim).filter(|s| !s.is_empty()).is_none()
}

Type guard

enum SpeechModelKind { Plain, VoiceDesign, VoiceClone }
fn speech_model_kind(model: &str) -> SpeechModelKind {
    let m = model.to_ascii_lowercase();
    if m.contains("voicedesign") { SpeechModelKind::VoiceDesign }
    else if m.contains("voiceclone") { SpeechModelKind::VoiceClone }
    else { SpeechModelKind::Plain }
}

Prevention

When it happens

Trigger: Using a `*voiceclone*` model with neither `--clone-voice` nor `--voice` supplied; passing a `--voice` value that is only whitespace (trimmed and filtered, so treated as absent).

Common situations: Selecting the voice-clone model without a reference recording ready; assuming clone models fall back to a default voice; migrating a script from a plain TTS model to the clone model without adding the audio argument.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/8246a879ef44373e. Report an issue: GitHub.