{"record":{"id":"2cefef344007b499","repo":"zeroclaw-labs/zeroclaw","slug":"telegram-file-download-failed","errorCode":null,"errorMessage":"Telegram file download failed: {}","messagePattern":"Telegram file download failed: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/telegram.rs","lineNumber":2043,"sourceCode":"        {\n            return Ok(path.to_string());\n        }\n\n        Err(FileLookupError::classify(status, body.as_ref()))\n    }\n\n    /// Download a file from the Telegram CDN.\n    async fn download_file(&self, file_path: &str) -> anyhow::Result<Vec<u8>> {\n        let url = format!(\"{}/file/bot{}/{file_path}\", self.api_base, self.bot_token);\n        let resp = self\n            .http_client()\n            .get(&url)\n            .send()\n            .await\n            .context(\"Failed to download Telegram file\")?;\n\n        if !resp.status().is_success() {\n            anyhow::bail!(\"Telegram file download failed: {}\", resp.status());\n        }\n\n        Ok(resp.bytes().await?.to_vec())\n    }\n\n    /// Extract (file_id, duration) from a voice or audio message.\n    fn parse_voice_metadata(message: &serde_json::Value) -> Option<(String, u64)> {\n        let voice = message.get(\"voice\").or_else(|| message.get(\"audio\"))?;\n        let file_id = voice.get(\"file_id\")?.as_str()?.to_string();\n        let duration = voice\n            .get(\"duration\")\n            .and_then(serde_json::Value::as_u64)\n            .unwrap_or(0);\n        Some((file_id, duration))\n    }\n\n    /// Extract attachment metadata from an incoming Telegram message (document or photo).\n    /// Returns `None` for text-only, voice, and other unsupported message types.","sourceCodeStart":2025,"sourceCodeEnd":2061,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/telegram.rs#L2025-L2061","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet path = get_file(&file_id).await?; // file_path possibly stale\ndownload(path).await?;\n\n// after\nlet path = get_file(&file_id).await?; // always re-request before download\ndownload(&format!(\"{api_base}/file/bot{token}/{path}\")).await?;","handlingStrategy":"retry","validationCode":"// mint a fresh file_path right before downloading\nlet file_path = channel.get_file_path(&file_id).await?;\nlet url = format!(\"{api_base}/file/bot{token}/{file_path}\");","typeGuard":"fn is_expired_file_link(err: &anyhow::Error) -> bool {\n    let s = err.to_string();\n    s.contains(\"Telegram file download failed: 404\") || s.contains(\"file_path invalid\")\n}","tryCatchPattern":"for attempt in 0..2 {\n    match channel.download_file(&file_id).await {\n        Err(e) if attempt == 0 && is_expired_file_link(&e) => {\n            continue; // re-request getFile and retry once\n        }\n        other => return other,\n    }\n}","preventionTips":["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."],"tags":["telegram","getfile","download","expired-link","bot-api"],"backgroundTag":"telegram-file-download-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}