Hmbown/CodeWhale · error

Speech synthesis failed: HTTP {status}: {error_text}

Error message

Speech synthesis failed: HTTP {status}: {error_text}

What it means

The speech-synthesis call posts to `{base_url}/chat/completions` (Xiaomi MiMo TTS shape) and bails when the HTTP status is non-2xx. The error body is read with a 64KB cap and passed through `sanitize_http_error_body` with the provider display name, so leaked keys in the body are redacted before the message is shown. All earlier validations (provider, model, text, voice requirements) have already passed when this fires.

Source

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

            "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;
            let error_text = sanitize_http_error_body(
                Some(self.api_provider.display_name()),
                status.as_u16(),
                &raw_error_text,
            );
            anyhow::bail!("Speech synthesis failed: HTTP {status}: {error_text}");
        }

        let response_text = response
            .text()
            .await
            .context("Failed to read speech synthesis response body")?;
        let payload: Value = serde_json::from_str(&response_text)
            .context("Failed to parse speech synthesis response JSON")?;
        let (audio_bytes, transcript) = parse_speech_audio_response(&payload)?;

        Ok(SpeechSynthesisResponse {
            model,
            audio_format,
            audio_bytes,
            transcript,
            voice,
        })
    }

View on GitHub (pinned to 8880682c63)

Solutions

  1. Read the embedded provider error text — it is the sanitized upstream body and names the actual cause (auth, quota, invalid parameter).
  2. 401/403 → refresh the xiaomi-mimo credential in config and re-run.
  3. 400 → verify the audio format string and that the voice data-URI/clone file is valid and not truncated.
  4. 429/5xx → wait and retry; if persistent, check the provider status page and your quota.
Defensive patterns

Strategy: try-catch

Try / catch

match client.synthesize_speech(req).await {
    Ok(audio) => play(audio),
    Err(e) => {
        let msg = e.to_string();
        if msg.contains("HTTP 401") || msg.contains("HTTP 403") { refresh_credentials(); }
        else if msg.contains("HTTP 429") { schedule_retry(backoff()); }
        else { show_sanitized_error(msg); }
    }
}

Prevention

When it happens

Trigger: 401/403 bad or expired Xiaomi MiMo credentials; 400 invalid `audio.format` value or malformed voice data-URI; 429 quota/rate exhaustion on the MiMo endpoint; 5xx upstream failures; wrong base_url routing TTS traffic to a non-MiMo gateway.

Common situations: Expired API key for the xiaomi-mimo provider; audio format string not in the accepted set (check `normalize_audio_format` accepted values); oversized clone-voice payload rejected upstream; regional endpoint quotas.

Related errors


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