zeroclaw-labs/zeroclaw · error · anyhow::Error
Telegram file download failed: {}
Error message
Telegram file download failed: {} What it means
After obtaining a file_path via getFile, the channel streams the bytes from the Bot API file URL and bails with the raw status when that download answers non-2xx (transport-level failures surface earlier as the 'Failed to download Telegram file' context). Rejections here are almost always expired or mismatched download credentials, since Telegram file links are short-lived and token-bound.
Source
Thrown at crates/zeroclaw-channels/src/telegram.rs:2043
{
return Ok(path.to_string());
}
Err(FileLookupError::classify(status, body.as_ref()))
}
/// Download a file from the Telegram CDN.
async fn download_file(&self, file_path: &str) -> anyhow::Result<Vec<u8>> {
let url = format!("{}/file/bot{}/{file_path}", self.api_base, self.bot_token);
let resp = self
.http_client()
.get(&url)
.send()
.await
.context("Failed to download Telegram file")?;
if !resp.status().is_success() {
anyhow::bail!("Telegram file download failed: {}", resp.status());
}
Ok(resp.bytes().await?.to_vec())
}
/// Extract (file_id, duration) from a voice or audio message.
fn parse_voice_metadata(message: &serde_json::Value) -> Option<(String, u64)> {
let voice = message.get("voice").or_else(|| message.get("audio"))?;
let file_id = voice.get("file_id")?.as_str()?.to_string();
let duration = voice
.get("duration")
.and_then(serde_json::Value::as_u64)
.unwrap_or(0);
Some((file_id, duration))
}
/// Extract attachment metadata from an incoming Telegram message (document or photo).
/// Returns `None` for text-only, voice, and other unsupported message types.View on GitHub (pinned to 88bb9c8533)
Solutions
- Call getFile again with the same file_id right before downloading to mint a fresh file_path.
- Ensure the same bot token is used for both getFile and the /file/bot<token>/ URL.
- For files above 20MB, run a local telegram-bot-api-server and point api_base at it.
- Process voice/media messages promptly instead of draining a long backlog.
Example fix
// before
let path = get_file(&file_id).await?; // file_path possibly stale
download(path).await?;
// after
let path = get_file(&file_id).await?; // always re-request before download
download(&format!("{api_base}/file/bot{token}/{path}")).await?; Defensive patterns
Strategy: retry
Validate before calling
// mint a fresh file_path right before downloading
let file_path = channel.get_file_path(&file_id).await?;
let url = format!("{api_base}/file/bot{token}/{file_path}"); Type guard
fn is_expired_file_link(err: &anyhow::Error) -> bool {
let s = err.to_string();
s.contains("Telegram file download failed: 404") || s.contains("file_path invalid")
} Try / catch
for attempt in 0..2 {
match channel.download_file(&file_id).await {
Err(e) if attempt == 0 && is_expired_file_link(&e) => {
continue; // re-request getFile and retry once
}
other => return other,
}
} Prevention
- Re-call getFile immediately before each download; never cache file_path across restarts or long queues.
- Use one bot token for getFile and the download URL.
- Process media updates promptly; drain backlogs in batches under an hour.
When it happens
Trigger: file_path expired — Bot API download links live roughly one hour, so processing queued/backfilled voice messages later fails; a different bot token used to build the download URL than the one that called getFile; file larger than the cloud Bot API 20MB download limit.
Common situations: Offline catch-up after downtime reprocessing old updates; two bots sharing one config so file_id and token disagree; large voice notes on the cloud API where getFile itself still succeeds.
Related errors
- {method} failed: status={status}, body={body}
- {method} by URL failed: {err}
- Telegram sendDocument by URL failed: {err}
- Telegram sendPhoto by URL failed: {err}
- Telegram sendMessage (approval) failed ({status}): {err}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/2cefef344007b499.
Report an issue: GitHub.