{"record":{"id":"cfc6de84a2ece116","repo":"zeroclaw-labs/zeroclaw","slug":"method-failed-status-status-body-body","errorCode":null,"errorMessage":"{method} failed: status={status}, body={body}","messagePattern":"(.+?) failed: status=(.+?), body=(.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/telegram.rs","lineNumber":1503,"sourceCode":"\n        let mut form = reqwest::multipart::Form::new()\n            .text(\"chat_id\", chat_id.to_string())\n            .part(\n                field,\n                reqwest::multipart::Part::bytes(audio_bytes)\n                    .file_name(filename)\n                    .mime_str(mime)?,\n            );\n\n        if let Some(tid) = thread_id {\n            form = form.text(\"message_thread_id\", tid.to_string());\n        }\n\n        let resp = client.post(&url).multipart(form).send().await?;\n        if !resp.status().is_success() {\n            let status = resp.status();\n            let body = resp.text().await.unwrap_or_default();\n            anyhow::bail!(\"{method} failed: status={status}, body={body}\");\n        }\n\n        ::zeroclaw_log::record!(\n            INFO,\n            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)\n                .with_attrs(::serde_json::json!({\"audio_len\": audio_len})),\n            \"sent voice note ( bytes)\"\n        );\n        Ok(())\n    }\n\n    async fn classify_edit_message_response(resp: reqwest::Response) -> EditMessageResult {\n        if resp.status().is_success() {\n            return EditMessageResult::Success;\n        }\n\n        let status = resp.status();\n        let body = resp.text().await.unwrap_or_default();","sourceCodeStart":1485,"sourceCodeEnd":1521,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/telegram.rs#L1485-L1521","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the body= field first — Telegram's `description` names the concrete problem.","On 401/404, verify the bot token with getMe; the token is wrong or revoked.","Confirm the upload really is OGG/Opus (the spec assumes synthesize_opus output); re-check the ffmpeg transcode step.","On 429, honor retry_after in the body and resend the same multipart request once after the delay."],"exampleFix":"// before\nlet resp = client.post(&url).multipart(form).send().await?;\n\n// after\nlet resp = client.post(&url).multipart(form).send().await?;\nif resp.status().as_u16() == 429 {\n    let body: serde_json::Value = resp.json().await?;\n    let wait = body[\"parameters\"][\"retry_after\"].as_u64().unwrap_or(1);\n    tokio::time::sleep(Duration::from_secs(wait)).await;\n    // resend once\n}","handlingStrategy":"try-catch","validationCode":"const VOICE_LIMIT: u64 = 50 * 1024 * 1024;\nlet meta = tokio::fs::metadata(&ogg_path).await?;\nif meta.len() > VOICE_LIMIT {\n    anyhow::bail!(\"voice file {}MB exceeds 50MB\", meta.len() / 1024 / 1024);\n}","typeGuard":"fn telegram_retry_after(err: &anyhow::Error) -> Option<u64> {\n    err.to_string().contains(\"429\")\n        .then(|| err.to_string().split(\"retry_after\").nth(1)?.parse().ok())\n        .flatten()\n}","tryCatchPattern":"if let Err(e) = send_voice_note(&chat, &path).await {\n    if let Some(secs) = telegram_retry_after(&e) {\n        tokio::time::sleep(Duration::from_secs(secs)).await;\n        send_voice_note(&chat, &path).await?;\n    } else {\n        return Err(e);\n    }\n}","preventionTips":["Always route voice bytes through the same OGG/Opus transcode so mime and format match telegram_audio_send_spec.","Honor retry_after on 429 and pace outbound voice messages.","Verify the bot token with getMe at startup so 401s fail fast."],"tags":["telegram","sendvoice","multipart","bot-api","rate-limit"],"backgroundTag":"telegram-file-upload-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}