zeroclaw-labs/zeroclaw · error

Telegram sendPhoto failed: {err}

Error message

Telegram sendPhoto failed: {err}

What it means

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.

Source

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

        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("sendPhoto"))
            .multipart(form)
            .send()
            .await?;

        if !resp.status().is_success() {
            let err = resp.text().await?;
            anyhow::bail!("Telegram sendPhoto 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})),
            "photo sent to"
        );
        Ok(())
    }

    /// Send a photo from bytes (in-memory) to a Telegram chat
    pub async fn send_photo_bytes(
        &self,
        chat_id: &str,
        thread_id: Option<&str>,
        file_bytes: Vec<u8>,
        file_name: &str,

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Read the embedded `description` for the exact Bot API reason.
  2. Check the image decodes (`file`/`identify` on the path) and is JPEG/PNG/WebP under 10MB.
  3. Send oversized images as documents via send_document to preserve resolution.
  4. On 429, honor retry_after; on 403, stop sending to that chat.
Defensive patterns

Strategy: validation

Validate before calling

const PHOTO_LIMIT: u64 = 10 * 1024 * 1024;
let meta = tokio::fs::metadata(file_path).await?;
anyhow::ensure!(meta.len() <= PHOTO_LIMIT, "photo {}MB exceeds 10MB; send as document", meta.len() / 1024 / 1024);

Type guard

fn looks_like_image(path: &Path) -> bool {
    matches!(path.extension().and_then(|e| e.to_str()), Some("jpg" | "jpeg" | "png" | "webp"))
}

Try / catch

if let Err(e) = channel.send_photo(chat, thread, path, caption).await {
    if e.to_string().contains("413") || e.to_string().contains("too big") {
        return channel.send_document(chat, thread, path, caption).await;
    }
    return Err(e);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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