{"record":{"id":"4ad2b9b37f0a6290","repo":"Hmbown/CodeWhale","slug":"speech-text-cannot-be-empty","errorCode":null,"errorMessage":"Speech text cannot be empty","messagePattern":"Speech text cannot be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/client.rs","lineNumber":2349,"sourceCode":"    /// voice-clone performance and is not spoken verbatim.\n    pub async fn synthesize_speech(\n        &self,\n        request: SpeechSynthesisRequest,\n    ) -> Result<SpeechSynthesisResponse> {\n        if self.api_provider != crate::config::ApiProvider::XiaomiMimo {\n            anyhow::bail!(\n                \"speech synthesis requires provider 'xiaomi-mimo' (current: {})\",\n                self.api_provider.as_str()\n            );\n        }\n\n        let model = request.model.trim().to_string();\n        if model.is_empty() {\n            anyhow::bail!(\"Speech model cannot be empty\");\n        }\n        let text = request.text.trim().to_string();\n        if text.is_empty() {\n            anyhow::bail!(\"Speech text cannot be empty\");\n        }\n\n        let audio_format = normalize_audio_format(&request.audio_format);\n        let model = wire_model_for_provider_route(self.api_provider, &self.base_url, &model);\n        let model_lower = model.to_ascii_lowercase();\n        let instruction = request\n            .instruction\n            .as_deref()\n            .map(str::trim)\n            .filter(|value| !value.is_empty());\n        let voice = request\n            .voice\n            .as_deref()\n            .map(str::trim)\n            .filter(|value| !value.is_empty())\n            .map(str::to_string);\n\n        if model_lower.contains(\"voicedesign\") && instruction.is_none() {","sourceCodeStart":2331,"sourceCodeEnd":2367,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/client.rs#L2331-L2367","documentation":"Second input validation in `synthesize_speech`: the text to speak is trimmed and must be non-empty. An empty or whitespace-only `SpeechSynthesisRequest.text` is rejected locally before any request body is built — no network round trip occurs.","triggerScenarios":"Running the speech command with no positional text argument; passing a string of only spaces/newlines; scripting with an empty text variable; quoting errors that swallow the intended text (`/speech --model m \"\"`).","commonSituations":"Shell quoting mistakes dropping the argument; pipelines feeding the tool an empty string (e.g. `$(grep -q ... && echo)` producing nothing); UI sending the placeholder text instead of user input.","solutions":["Supply non-blank text to speak after the options: `/speech --model xiaomi-mimo-tts \"Narrate this\"`.","Guard the caller side: skip or error early when the trimmed input is empty instead of invoking the API.","When piping text, fail fast on empty stdin before calling the tool."],"exampleFix":"# before\n/speech --model xiaomi-mimo-tts \"   \"\n# after\n/speech --model xiaomi-mimo-tts \"Weekly report summary...\"","handlingStrategy":"validation","validationCode":"fn speech_request_valid(model: &str, text: &str) -> bool {\n    !model.trim().is_empty() && !text.trim().is_empty()\n}","typeGuard":"fn non_blank(s: &str) -> Option<&str> {\n    let t = s.trim();\n    (!t.is_empty()).then_some(t)\n}","tryCatchPattern":null,"preventionTips":["Reject empty stdin/arguments at the CLI or UI layer with a specific 'text is empty' message.","When piping text into the tool, fail fast if the pipeline yields only whitespace.","Show a character count in speech UIs so an empty payload is visible before submit."],"tags":["speech","tts","validation","input-validation"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}