{"record":{"id":"d165ca5a41c27675","repo":"zeroclaw-labs/zeroclaw","slug":"telegram-sendvoice-failed-err","errorCode":null,"errorMessage":"Telegram sendVoice failed: {err}","messagePattern":"Telegram sendVoice failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/telegram.rs","lineNumber":3462,"sourceCode":"\n        if let Some(tid) = thread_id {\n            form = form.text(\"message_thread_id\", tid.to_string());\n        }\n\n        if let Some(cap) = caption {\n            form = form.text(\"caption\", cap.to_string());\n        }\n\n        let resp = self\n            .http_client()\n            .post(self.api_url(\"sendVoice\"))\n            .multipart(form)\n            .send()\n            .await?;\n\n        if !resp.status().is_success() {\n            let err = resp.text().await?;\n            anyhow::bail!(\"Telegram sendVoice failed: {err}\");\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!({\"chat_id\": chat_id, \"file_name\": file_name})),\n            \"voice sent to\"\n        );\n        Ok(())\n    }\n\n    /// Send a file by URL (Telegram will download it)\n    pub async fn send_document_by_url(\n        &self,\n        chat_id: &str,\n        thread_id: Option<&str>,\n        url: &str,\n        caption: Option<&str>,","sourceCodeStart":3444,"sourceCodeEnd":3480,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/telegram.rs#L3444-L3480","documentation":"sendVoice() uploads a multipart sendVoice request and bails with Telegram's response body on non-2xx. Telegram only accepts voice notes as OGG encoded with Opus, so this error fires when that contract or the chat itself is broken.","triggerScenarios":"400 'wrong type of the current file' when the upload is OGG/Vorbis, WAV, or MP3 instead of OGG/Opus; 400 chat not found / invalid thread_id; 403 blocked/kicked; 429 flood control; oversized voice buffers.","commonSituations":"TTS pipeline skipping the ffmpeg Opus transcode after a config change; ffmpeg writing default vorbis codec; speech clips converted with the wrong flags; rate limits during rapid conversational replies.","solutions":["Read the embedded `description` for the exact reason.","Verify the file is OGG/Opus: `ffprobe file.ogg` should report codec=opus; re-encode with `ffmpeg -i in.wav -c:a libopus out.ogg`.","Confirm chat_id/thread_id are valid and the bot is not blocked.","On 429, honor retry_after and resend once."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// confirm the payload is OGG/Opus before uploading\nlet head = tokio::fs::read(&path).await?;\nanyhow::ensure!(head.starts_with(b\"OggS\"), \"voice file is not an OGG container\");","typeGuard":"fn is_ogg_opus(path: &Path) -> bool {\n    // cheap header check; ffprobe -c:a opus is authoritative\n    std::fs::read(path).map(|b| b.starts_with(b\"OggS\")).unwrap_or(false)\n}","tryCatchPattern":"if let Err(e) = channel.send_voice(chat, thread, path, None).await {\n    if e.to_string().contains(\"wrong type of the current file\") {\n        let fixed = transcode_to_opus(path).await?; // ffmpeg -c:a libopus\n        return channel.send_voice(chat, thread, &fixed, None).await;\n    }\n    return Err(e);\n}","preventionTips":["Always produce voice notes with `ffmpeg -c:a libopus` into .ogg; never rely on default codecs.","Add a pipeline test that fails when TTS output is not OggS+Opus.","Keep chat_id/thread_id valid and honor 429 retry_after."],"tags":["telegram","sendvoice","multipart","opus","upload"],"backgroundTag":"telegram-file-upload-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}