zeroclaw-labs/zeroclaw · error
Telegram sendPhoto by URL failed: {err}
Error message
Telegram sendPhoto by URL failed: {err} What it means
The URL variant of photo sending posts a JSON body (sendPhoto with a URL) and bails with Telegram's response body on non-2xx. Telegram's servers fetch and decode the image themselves, so rejections typically mean the URL did not yield a usable image.
Source
Thrown at crates/zeroclaw-channels/src/telegram.rs:3546
if let Some(tid) = thread_id {
body["message_thread_id"] = serde_json::Value::String(tid.to_string());
}
if let Some(cap) = caption {
body["caption"] = serde_json::Value::String(cap.to_string());
}
let resp = self
.http_client()
.post(self.api_url("sendPhoto"))
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
let err = resp.text().await?;
anyhow::bail!("Telegram sendPhoto by URL 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, "url": url})),
"photo (URL) sent to"
);
Ok(())
}
/// Send a video by URL (Telegram will download it)
pub async fn send_video_by_url(
&self,
chat_id: &str,
thread_id: Option<&str>,
url: &str,
caption: Option<&str>,View on GitHub (pinned to 88bb9c8533)
Solutions
- curl -I the URL: expect 200 with image/jpeg or image/png content-type and no login redirect.
- Use the direct asset URL; for protected hosts, download and use send_photo multipart instead.
- Read the embedded `description` and match it against the URL's actual response.
- On 429, honor retry_after and resend.
Defensive patterns
Strategy: fallback
Validate before calling
let resp = reqwest::Client::new().head(url).send().await?;
anyhow::ensure!(resp.status().is_success(), "image URL not fetchable: {}", resp.status());
let ct = resp.headers().get(reqwest::header::CONTENT_TYPE).and_then(|v| v.to_str().ok()).unwrap_or("");
anyhow::ensure!(ct.starts_with("image/"), "URL content-type is {ct}, not an image"); Type guard
fn is_photo_url_failure(err: &anyhow::Error) -> bool {
let s = err.to_string();
s.contains("failed to get HTTP URL content") || s.contains("wrong type of the current file")
} Try / catch
if let Err(e) = channel.send_photo_by_url(chat, url).await {
if is_photo_url_failure(&e) {
let bytes = reqwest::get(url).await?.bytes().await?;
return channel.send_photo_bytes(chat, thread, bytes.to_vec(), "photo.jpg", None).await;
}
return Err(e);
} Prevention
- Pass the direct asset URL, never an HTML page or redirect chain.
- Verify content-type is image/jpeg|png|webp with a HEAD pre-check.
- Keep a local-download-then-multipart fallback for protected hosts.
When it happens
Trigger: 400 'failed to get HTTP URL content' when the URL returns HTML, a redirect chain, or an unsupported format (SVG, AVIF); 400 'wrong type of the current file' for non-image content; 403 chat not found/blocked; 429 rate limit.
Common situations: Chart/image URLs that 302 to a login page; webp variants Telegram's fetcher rejects; expired signed URLs; page URLs pasted instead of the direct image asset.
Related errors
- {method} by URL failed: {err}
- Telegram sendPhoto failed: {err}
- Telegram sendDocument by URL failed: {err}
- {method} failed: status={status}, body={body}
- Telegram file download failed: {}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/4a2ea2ff919d86f3.
Report an issue: GitHub.