zeroclaw-labs/zeroclaw · error · anyhow::Error

{method} failed: status={status}, body={body}

Error message

{method} failed: status={status}, body={body}

What it means

The voice-note upload path builds a multipart POST where method/field/mime come from telegram_audio_send_spec("opus") (sendVoice with OGG/Opus) and bails on any non-2xx, embedding the HTTP status and Telegram response body. The body's `description` field names the exact Bot API rejection.

Source

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

        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 {
            form = form.text("message_thread_id", tid.to_string());
        }

        let resp = client.post(&url).multipart(form).send().await?;
        if !resp.status().is_success() {
            let status = resp.status();
            let body = resp.text().await.unwrap_or_default();
            anyhow::bail!("{method} failed: status={status}, body={body}");
        }

        ::zeroclaw_log::record!(
            INFO,
            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)
                .with_attrs(::serde_json::json!({"audio_len": audio_len})),
            "sent voice note ( bytes)"
        );
        Ok(())
    }

    async fn classify_edit_message_response(resp: reqwest::Response) -> EditMessageResult {
        if resp.status().is_success() {
            return EditMessageResult::Success;
        }

        let status = resp.status();
        let body = resp.text().await.unwrap_or_default();

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Read the body= field first — Telegram's `description` names the concrete problem.
  2. On 401/404, verify the bot token with getMe; the token is wrong or revoked.
  3. Confirm the upload really is OGG/Opus (the spec assumes synthesize_opus output); re-check the ffmpeg transcode step.
  4. On 429, honor retry_after in the body and resend the same multipart request once after the delay.

Example fix

// before
let resp = client.post(&url).multipart(form).send().await?;

// after
let resp = client.post(&url).multipart(form).send().await?;
if resp.status().as_u16() == 429 {
    let body: serde_json::Value = resp.json().await?;
    let wait = body["parameters"]["retry_after"].as_u64().unwrap_or(1);
    tokio::time::sleep(Duration::from_secs(wait)).await;
    // resend once
}
Defensive patterns

Strategy: try-catch

Validate before calling

const VOICE_LIMIT: u64 = 50 * 1024 * 1024;
let meta = tokio::fs::metadata(&ogg_path).await?;
if meta.len() > VOICE_LIMIT {
    anyhow::bail!("voice file {}MB exceeds 50MB", meta.len() / 1024 / 1024);
}

Type guard

fn telegram_retry_after(err: &anyhow::Error) -> Option<u64> {
    err.to_string().contains("429")
        .then(|| err.to_string().split("retry_after").nth(1)?.parse().ok())
        .flatten()
}

Try / catch

if let Err(e) = send_voice_note(&chat, &path).await {
    if let Some(secs) = telegram_retry_after(&e) {
        tokio::time::sleep(Duration::from_secs(secs)).await;
        send_voice_note(&chat, &path).await?;
    } else {
        return Err(e);
    }
}

Prevention

When it happens

Trigger: 400 'wrong type of the current file' / duration errors when bytes are not valid OGG/Opus; 401 Unauthorized from a wrong bot_token; 413 when audio exceeds Telegram's voice size limit; 429 flood control (retry_after in body); 400 chat not found or 403 bot blocked by the user.

Common situations: ffmpeg producing a non-Opus container after a codec change; token belonging to a different bot than the chat; bursts of voice replies hitting per-chat rate limits; long replies exceeding voice-duration expectations.

Related errors


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