zeroclaw-labs/zeroclaw · error · anyhow::Error
TTS returned empty audio
Error message
TTS returned empty audio
What it means
synthesize_and_send_voice() calls TtsManager::synthesize_opus (which transcodes to OGG/Opus via ffmpeg internally) and guards against a zero-byte result before uploading a voice note. Empty audio means the TTS provider or the ffmpeg transcode stage produced nothing, and the library refuses to send an empty file.
Source
Thrown at crates/zeroclaw-channels/src/telegram.rs:1477
async fn synthesize_and_send_voice(
api_base: &str,
bot_token: &str,
chat_id: &str,
thread_id: Option<&str>,
text: &str,
tts_manager: &crate::tts::TtsManager,
) -> anyhow::Result<()> {
let audio_bytes = tts_manager.synthesize_opus(text).await?;
let audio_len = audio_bytes.len();
::zeroclaw_log::record!(
INFO,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)
.with_attrs(::serde_json::json!({"audio_len": audio_len})),
"synthesized bytes of audio"
);
if audio_bytes.is_empty() {
anyhow::bail!("TTS returned empty audio");
}
// synthesize_opus already transcodes to OGG/Opus via ffmpeg internally
let (method, field, filename, mime) = telegram_audio_send_spec("opus")?;
let url = format!("{api_base}/bot{bot_token}/{method}");
let client = zeroclaw_config::schema::build_runtime_proxy_client("channel.telegram");
let mut form = reqwest::multipart::Form::new()
.text("chat_id", chat_id.to_string())
.part(
field,
reqwest::multipart::Part::bytes(audio_bytes)
.file_name(filename)
.mime_str(mime)?,
);
if let Some(tid) = thread_id {View on GitHub (pinned to 88bb9c8533)
Solutions
- Verify ffmpeg is installed and on PATH (`ffmpeg -version`); synthesize_opus depends on it for the OGG/Opus transcode.
- Check the TTS provider credentials and quota in config, and test tts_manager.synthesize_opus standalone with plain text.
- Skip voice and fall back to a text reply when input text is empty or synthesis yields nothing — the caller already logs-and-continues at the TTS error site.
- Add provider-response logging upstream so the real error surfaces instead of an empty buffer.
Example fix
// before
let audio = tts_manager.synthesize_opus(text).await?;
// after
if text.trim().is_empty() {
return send_text_chunks(text, chat_id, thread_id).await;
}
let audio = tts_manager.synthesize_opus(text).await?;
if audio.is_empty() {
tracing::warn!("TTS produced empty audio; falling back to text");
return send_text_chunks(text, chat_id, thread_id).await;
} Defensive patterns
Strategy: fallback
Validate before calling
if text.trim().is_empty() {
// no point synthesizing: send text (or skip) instead
return send_text_chunks(text, chat_id, thread_id).await;
}
let audio = tts_manager.synthesize_opus(text).await?; Type guard
fn is_speakable(text: &str) -> bool {
!text.trim().is_empty()
} Try / catch
match synthesize_and_send_voice(...).await {
Err(e) if e.to_string().contains("TTS returned empty audio") => {
tracing::warn!("voice synthesis empty; sending text instead");
send_text_chunks(&text, chat_id, thread_id).await?;
}
other => other?,
} Prevention
- Check `ffmpeg -version` in container healthchecks since synthesize_opus depends on it.
- Alert on TTS provider auth/quota errors instead of letting them surface as empty buffers.
- Guard the pipeline: skip voice for empty/whitespace replies.
When it happens
Trigger: TTS provider returns HTTP 200 with an empty body (quota exhausted, invalid API key coerced to empty upstream, unsupported/emoji-only input text); ffmpeg missing from PATH or failing silently so the transcode writes 0 bytes; empty reply text after markdown stripping.
Common situations: Expired TTS provider credentials; ffmpeg absent in a slim Docker image; agent reply reduced to whitespace before voice synthesis; provider model change returning audio under a new field that parsing misses.
Related errors
- ffmpeg transcode to opus failed: {stderr}
- Telegram sendVoice failed: {err}
- Failed to speak: {}
- Telegram channel requires the `channel-telegram` feature
- Cannot persist empty Telegram identity
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/4a32d9e04426a1ed.
Report an issue: GitHub.