zeroclaw-labs/zeroclaw · error

Telegram sendAudio failed: {err}

Error message

Telegram sendAudio failed: {err}

What it means

send_audio() uploads an audio file as multipart sendAudio and bails with Telegram's response body on non-2xx. Audio expects a playable music track (MP3/M4A with metadata), and errors commonly stem from format mismatches or chat permissions.

Source

Thrown at crates/zeroclaw-channels/src/telegram.rs:3413

        if let Some(tid) = thread_id {
            form = form.text("message_thread_id", tid.to_string());
        }

        if let Some(cap) = caption {
            form = form.text("caption", cap.to_string());
        }

        let resp = self
            .http_client()
            .post(self.api_url("sendAudio"))
            .multipart(form)
            .send()
            .await?;

        if !resp.status().is_success() {
            let err = resp.text().await?;
            anyhow::bail!("Telegram sendAudio failed: {err}");
        }

        ::zeroclaw_log::record!(
            INFO,
            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)
                .with_attrs(::serde_json::json!({"chat_id": chat_id, "file_name": file_name})),
            "audio sent to"
        );
        Ok(())
    }

    /// Send a voice message to a Telegram chat
    pub async fn send_voice(
        &self,
        chat_id: &str,
        thread_id: Option<&str>,
        file_path: &Path,
        caption: Option<&str>,

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Read the embedded `description` first.
  2. Ensure the payload is MP3/M4A; OGG/Opus speech belongs to sendVoice.
  3. Check size against 50MB and re-encode if needed.
  4. On 403 stop sending to the chat; on 429 honor retry_after.
Defensive patterns

Strategy: try-catch

Validate before calling

const AUDIO_LIMIT: u64 = 50 * 1024 * 1024;
let meta = tokio::fs::metadata(file_path).await?;
anyhow::ensure!(meta.len() <= AUDIO_LIMIT, "audio {}MB exceeds 50MB", meta.len() / 1024 / 1024);
anyhow::ensure!(!matches!(path.extension().and_then(|e| e.to_str()), Some("ogg" | "opus")), "speech OGG/Opus belongs to sendVoice, not sendAudio");

Type guard

fn is_music_track(path: &Path) -> bool {
    matches!(path.extension().and_then(|e| e.to_str()), Some("mp3" | "m4a"))
}

Try / catch

if !is_music_track(path) {
    return channel.send_voice(chat, thread, path, None).await;
}
channel.send_audio(chat, thread, path, caption).await?;

Prevention

When it happens

Trigger: 413 when audio exceeds the 50MB cap; 400 when the bytes are not MP3/M4A (e.g. raw Opus or WAV sent as audio); 400 chat not found / invalid thread_id; 403 blocked/kicked; 429 flood control.

Common situations: Sending TTS output meant as a voice note (OGG/Opus) through sendAudio instead of sendVoice; large podcast/wav files; chat permission lost between queueing and delivery.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/2a79b9225458c82c. Report an issue: GitHub.