Hmbown/CodeWhale · error · anyhow::Error

speech requires a TTS model

Error message

speech requires a TTS model (examples: {}); got {model}

What it means

The speech subcommand requires a text-to-speech model; after inferring the model (from --model or defaults based on voice/voice-prompt usage), it checks the lowercase model name contains "tts" and bails otherwise, listing example TTS model names from SPEECH_MODEL_EXAMPLES.

Solutions

  1. Pass an explicit TTS model via --model (see the example names in the error message)
  2. Use a model name containing "tts", e.g. mimo-v2.5-tts or the voicedesign/voiceclone variants
  3. Fix the config default model so it does not supply a chat model to speech

Example fix

// before
codewhale speech --model mimo-v2.5 --text "hi"
// after
codewhale speech --model mimo-v2.5-tts --text "hi"
Defensive patterns

Strategy: validation

Validate before calling

if !model.to_ascii_lowercase().contains("tts") {
    eprintln!("speech requires a TTS model, got {model}");
    std::process::exit(2);
}

Prevention

When it happens

Trigger: Passing --model with a non-TTS model name (e.g. a chat model like mimo-v2.5) to `speech`, or a config default model that is not a TTS model.

Common situations: Reusing a chat model name from the speech command line; typos in the TTS model id; stale config defaults where the general model leaks into the speech command.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/51ec4d88d813abaf. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/lib.rs:7827

    if text.trim().is_empty() {
        bail!("Speech text cannot be empty");
    }
    let voice_is_data_uri = voice
        .as_deref()
        .map(str::trim)
        .is_some_and(|value| value.starts_with("data:audio/"));
    if clone_voice.is_some() && voice.is_some() {
        bail!("Use either --clone-voice or --voice for cloned voice data, not both");
    }
    let model = infer_speech_model(
        model.as_deref(),
        clone_voice.is_some() || voice_is_data_uri,
        voice_prompt.is_some(),
    );
    let model_lower = model.to_ascii_lowercase();
    if !model_lower.contains("tts") {
        bail!(
            "speech requires a TTS model (examples: {}); got {model}",
            SPEECH_MODEL_EXAMPLES.join(", ")
        );
    }
    let is_voice_design = model_lower.contains("voicedesign");
    let is_voice_clone = model_lower.contains("voiceclone");

    let instruction = combine_speech_instructions(instruction, voice_prompt);
    if is_voice_design
        && instruction
            .as_deref()
            .is_none_or(|value| value.trim().is_empty())
    {
        bail!(
            "mimo-v2.5-tts-voicedesign requires --voice-prompt or --instruction to describe the voice"
        );
    }

View on GitHub (pinned to 73e0f67d83)