{"record":{"id":"6e367feb92637c98","repo":"zeroclaw-labs/zeroclaw","slug":"telegram-senddocument-by-url-failed-err","errorCode":null,"errorMessage":"Telegram sendDocument by URL failed: {err}","messagePattern":"Telegram sendDocument by URL failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/telegram.rs","lineNumber":3504,"sourceCode":"\n        if let Some(tid) = thread_id {\n            body[\"message_thread_id\"] = serde_json::Value::String(tid.to_string());\n        }\n\n        if let Some(cap) = caption {\n            body[\"caption\"] = serde_json::Value::String(cap.to_string());\n        }\n\n        let resp = self\n            .http_client()\n            .post(self.api_url(\"sendDocument\"))\n            .json(&body)\n            .send()\n            .await?;\n\n        if !resp.status().is_success() {\n            let err = resp.text().await?;\n            anyhow::bail!(\"Telegram sendDocument by URL 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, \"url\": url})),\n            \"document (URL) sent to\"\n        );\n        Ok(())\n    }\n\n    /// Send a photo by URL (Telegram will download it)\n    pub async fn send_photo_by_url(\n        &self,\n        chat_id: &str,\n        thread_id: Option<&str>,\n        url: &str,\n        caption: Option<&str>,","sourceCodeStart":3486,"sourceCodeEnd":3522,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/telegram.rs#L3486-L3522","documentation":"The URL variant of document sending posts a JSON body (sendDocument with a URL) and bails with Telegram's response body on non-2xx. Telegram fetches the URL server-side, so failures usually mean the URL was unreachable, the wrong content type, or the chat was undeliverable.","triggerScenarios":"400 'failed to get HTTP URL content' when Telegram cannot download the URL (private host, expired signed link, hotlink protection, HTML redirect instead of the file); malformed URL; 403 chat not found/blocked; document behind the URL over 20MB fetched by Telegram.","commonSituations":"Links to intranet/localhost artifact servers; presigned S3 URLs that expired before the send; CDN serving an HTML error page with 200; sending page links instead of direct file links.","solutions":["curl the URL from a public network — Telegram must be able to fetch exactly that file.","Use the direct file link with a document-appropriate Content-Type, not an HTML page.","For private hosts, download locally and use send_document (multipart) instead.","Read the embedded `description` for the specific Bot API reason."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"let resp = reqwest::Client::new().head(url).send().await?;\nanyhow::ensure!(resp.status().is_success(), \"document URL not fetchable: {}\", resp.status());\nanyhow::ensure!(!url.starts_with(\"http://localhost\") && !url.starts_with(\"http://127.\"), \"URL must be reachable by Telegram\");","typeGuard":"fn is_url_content_failure(err: &anyhow::Error) -> bool {\n    err.to_string().contains(\"failed to get HTTP URL content\")\n}","tryCatchPattern":"if let Err(e) = channel.send_document_by_url(chat, url).await {\n    if is_url_content_failure(&e) {\n        let bytes = reqwest::get(url).await?.bytes().await?;\n        return channel.send_document_bytes(chat, thread, bytes.to_vec(), name, None).await;\n    }\n    return Err(e);\n}","preventionTips":["Use direct, public, non-expiring file URLs for sendDocument-by-URL.","For private hosts, download locally and use the multipart path.","Pre-check with a HEAD request including expected content-type."],"tags":["telegram","senddocument","send-by-url","bot-api"],"backgroundTag":"telegram-send-by-url-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}