{"record":{"id":"a6baff9fdc3b4185","repo":"zeroclaw-labs/zeroclaw","slug":"telegram-sendphoto-failed-err","errorCode":null,"errorMessage":"Telegram sendPhoto failed: {err}","messagePattern":"Telegram sendPhoto failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/telegram.rs","lineNumber":3271,"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(\"sendPhoto\"))\n            .multipart(form)\n            .send()\n            .await?;\n\n        if !resp.status().is_success() {\n            let err = resp.text().await?;\n            anyhow::bail!(\"Telegram sendPhoto 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            \"photo sent to\"\n        );\n        Ok(())\n    }\n\n    /// Send a photo from bytes (in-memory) to a Telegram chat\n    pub async fn send_photo_bytes(\n        &self,\n        chat_id: &str,\n        thread_id: Option<&str>,\n        file_bytes: Vec<u8>,\n        file_name: &str,","sourceCodeStart":3253,"sourceCodeEnd":3289,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/telegram.rs#L3253-L3289","documentation":"send_photo() reads the image from disk and uploads it as multipart sendPhoto (defaulting the part filename to photo.jpg), bailing with Telegram's body on non-2xx. Telegram is strict about photo payloads, so most rejections are format/size related.","triggerScenarios":"400 when the bytes are not a decodable image (corrupt file, PNG renamed .jpg, SVG); 413 when the file exceeds the 10MB photo cap; 400 'chat not found' or bad thread_id; 403 blocked/kicked; 429 flood control.","commonSituations":"Sending screenshots that failed to finish writing before upload; image generation pipeline emitting webp/avif Telegram rejects; >10MB exports that should be sent as documents instead.","solutions":["Read the embedded `description` for the exact Bot API reason.","Check the image decodes (`file`/`identify` on the path) and is JPEG/PNG/WebP under 10MB.","Send oversized images as documents via send_document to preserve resolution.","On 429, honor retry_after; on 403, stop sending to that chat."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const PHOTO_LIMIT: u64 = 10 * 1024 * 1024;\nlet meta = tokio::fs::metadata(file_path).await?;\nanyhow::ensure!(meta.len() <= PHOTO_LIMIT, \"photo {}MB exceeds 10MB; send as document\", meta.len() / 1024 / 1024);","typeGuard":"fn looks_like_image(path: &Path) -> bool {\n    matches!(path.extension().and_then(|e| e.to_str()), Some(\"jpg\" | \"jpeg\" | \"png\" | \"webp\"))\n}","tryCatchPattern":"if let Err(e) = channel.send_photo(chat, thread, path, caption).await {\n    if e.to_string().contains(\"413\") || e.to_string().contains(\"too big\") {\n        return channel.send_document(chat, thread, path, caption).await;\n    }\n    return Err(e);\n}","preventionTips":["Send >10MB images as documents to keep full resolution.","Confirm files are fully written and valid JPEG/PNG/WebP before upload.","Route SVG/AVIF through a rasterization step first."],"tags":["telegram","sendphoto","multipart","upload","image"],"backgroundTag":"telegram-file-upload-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}