{"record":{"id":"95cf9463641eb0f5","repo":"Hmbown/CodeWhale","slug":"use-either-clone-voice-or-voice-for-cloned-voi","errorCode":null,"errorMessage":"Use either --clone-voice or --voice for cloned voice data, not both","messagePattern":"Use either --clone-voice or --voice for cloned voice data, not both","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/lib.rs","lineNumber":7369,"sourceCode":"        json: json_output,\n    } = args;\n\n    if config.api_provider() != ApiProvider::XiaomiMimo {\n        bail!(\n            \"`speech` requires provider = \\\"xiaomi-mimo\\\" (current: {}). Run with `--provider xiaomi-mimo` or set it in config.\",\n            config.api_provider().as_str()\n        );\n    }\n\n    if text.trim().is_empty() {\n        bail!(\"Speech text cannot be empty\");\n    }\n    let voice_is_data_uri = voice\n        .as_deref()\n        .map(str::trim)\n        .is_some_and(|value| value.starts_with(\"data:audio/\"));\n    if clone_voice.is_some() && voice.is_some() {\n        bail!(\"Use either --clone-voice or --voice for cloned voice data, not both\");\n    }\n    let model = infer_speech_model(\n        model.as_deref(),\n        clone_voice.is_some() || voice_is_data_uri,\n        voice_prompt.is_some(),\n    );\n    let model_lower = model.to_ascii_lowercase();\n    if !model_lower.contains(\"tts\") {\n        bail!(\n            \"speech requires a TTS model (examples: {}); got {model}\",\n            SPEECH_MODEL_EXAMPLES.join(\", \")\n        );\n    }\n    let is_voice_design = model_lower.contains(\"voicedesign\");\n    let is_voice_clone = model_lower.contains(\"voiceclone\");\n\n    let instruction = combine_speech_instructions(instruction, voice_prompt);\n    if is_voice_design","sourceCodeStart":7351,"sourceCodeEnd":7387,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/lib.rs#L7351-L7387","documentation":"Mutual-exclusion guard for cloned-voice input. `--clone-voice` (a path to an mp3/wav sample) and `--voice` (a voice id or a data:audio/... data-URI) are two alternative ways to supply cloned voice data; supplying both is ambiguous, so the CLI refuses the combination before encoding anything.","triggerScenarios":"`codewhale speech` invoked with both `--clone-voice <path>` and `--voice <value>` present (clone_voice.is_some() && voice.is_some()). The check runs before data-URI detection on --voice, so even a plain voice id conflicts with --clone-voice.","commonSituations":"Long speech commands grown flag by flag while prototyping; templates that always set --voice and later add --clone-voice for cloning experiments.","solutions":["Cloning from a local file: keep `--clone-voice <mp3|wav>` and drop `--voice`","Already have a data:audio/... data-URI: keep `--voice` and drop `--clone-voice`","Plain named voice for non-clone models: `--voice` alone is fine; --clone-voice exists only for cloning"],"exampleFix":"// before\n$ codewhale speech --clone-voice sample.mp3 --voice data:audio/mp3;base64,...\nError: Use either --clone-voice or --voice for cloned voice data, not both\n\n// after\n$ codewhale speech --clone-voice sample.mp3","handlingStrategy":"validation","validationCode":"if [ -n \"$CLONE_VOICE\" ] && [ -n \"$VOICE\" ]; then\n  echo 'pass only one of --clone-voice / --voice' >&2; exit 2\nfi","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pick exactly one voice channel per invocation and encode that policy in wrappers","Fail scripts at flag-parse time instead of relying on the CLI error"],"tags":["speech","tts","voice-cloning","mutually-exclusive-flags"],"backgroundTag":"mutually-exclusive-flags","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}