Hmbown/CodeWhale · error · anyhow::Error

Speech synthesis failed: HTTP

Error message

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

What it means

The speech synthesis HTTP request completed but the provider returned a non-success status. The client reads a bounded slice of the error body, passes it through `sanitize_http_error_body` (provider-attributed and redacted), and surfaces the provider's status and message so the real cause (auth, quota, bad voice payload, unsupported format) is visible. Raised in `crates/tui/src/client.rs` around line 3384.

Solutions

  1. Read the sanitized `error_text` in the message — it is the provider's own error payload; fix the cause it names.
  2. Verify the provider API key and account access to the requested model.
  3. Check quota/billing if the status is 402/429.
  4. For 4xx validation errors, check the audio format and voice payload.
Defensive patterns

Strategy: try-catch

Validate before calling

assert!(voice.as_deref().map(str::trim).filter(|v| !v.is_empty()).is_some(), "voice required");
assert!(["mp3", "wav"].contains(&audio_format.as_str()), "unsupported audio format");

Try / catch

match client.synthesize_speech(req).await {
    Err(e) if e.to_string().contains("Speech synthesis failed: HTTP") => {
        let status = extract_status(&e.to_string());
        match status {
            429 | 503 => retry_with_backoff(),
            401 | 403 => refresh_credentials(),
            _ => report_provider_error(&e),
        }
    }
    Err(e) => return Err(e),
    Ok(resp) => resp,
}

Prevention

When it happens

Trigger: Any non-2xx response from POST `{base_url}/chat/completions` for a speech synthesis request: invalid API key, exhausted credits/quota, malformed voice data URI, unsupported `audio_format`, model not available to the account.

Common situations: Expired or missing provider API keys; free-tier accounts hitting a model they cannot access; oversized or corrupt clone audio; region-restricted endpoints returning 403.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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

Appendix: source

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

            "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 73e0f67d83)