Hmbown/CodeWhale · error · anyhow::Error
Model ' ' requires cloned voice data. Pass --clone-voice…
Error message
Model '{model}' requires cloned voice data. Pass --clone-voice <mp3|wav> or --voice <data-uri>. What it means
Speech synthesis via a *voiceclone* model requires reference audio of the voice to clone. The client validates this before sending any request: if the resolved model name contains 'voiceclone' and no voice payload (`request.voice`, e.g. a data URI) was supplied, it fails fast with this error instead of producing a confusing provider-side 4xx. It is a local precondition check in `crates/tui/src/client.rs` around line 3360.
Solutions
- Pass `--clone-voice <mp3|wav>` so the CLI encodes the reference audio as a data URI into `voice`.
- Or pass `--voice <data-uri>` with an explicit data: URI payload for the reference audio.
- Verify the request struct's `voice` field is populated (non-empty after trim) before calling synthesis.
- If cloning is not intended, switch to a non-voiceclone model.
Example fix
// before codewhale speak --model elevenlabs_voiceclone_v1 --text "hello" // after codewhale speak --model elevenlabs_voiceclone_v1 --text "hello" --clone-voice ./my-voice.mp3
Defensive patterns
Strategy: validation
Validate before calling
fn ensure_voice_present(model: &str, voice: Option<&str>) -> Result<(), String> {
if model.to_ascii_lowercase().contains("voiceclone")
&& voice.map(str::trim).filter(|v| !v.is_empty()).is_none()
{
return Err(format!("Model '{model}' requires --clone-voice <mp3|wav> or --voice <data-uri>."));
}
Ok(())
} Type guard
fn has_voice(voice: Option<&str>) -> bool {
voice.map(str::trim).filter(|v| !v.is_empty()).is_some()
} Prevention
- Always pass --clone-voice when using a *voiceclone* model.
- Encode reference audio as a data URI, not a bare file path.
- Validate request.voice is non-empty before invoking synthesis.
When it happens
Trigger: Calling speech synthesis with a model whose lowercased name contains "voiceclone" while `request.voice` is None, empty, or whitespace-only. This happens when the user omits `--clone-voice <mp3|wav>` / `--voice <data-uri>` on the CLI, or passes a path that failed to load into a data URI upstream.
Common situations: Developers switching a script from a standard TTS model to a clone model and forgetting the reference audio; passing a plain file path instead of a data URI so the voice field is treated as absent; environment configs where the clone audio is resolved conditionally and silently skipped.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- A positive pull request number is required
- API key contains invalid control characters
- API key id must be lowercase hex characters — the part…
- API key input is unexpectedly large
- API key must be - UTF-8 bytes
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/8246a879ef44373e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/client.rs:3360
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 73e0f67d83)