{"record":{"id":"9ee26f3be4f96028","repo":"zeroclaw-labs/zeroclaw","slug":"telegram-sendmessage-draft-failed-err","errorCode":null,"errorMessage":"Telegram sendMessage (draft) failed: {err}","messagePattern":"Telegram sendMessage \\(draft\\) failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/telegram.rs","lineNumber":3863,"sourceCode":"\n        let mut body = serde_json::json!({\n            \"chat_id\": chat_id,\n            \"text\": initial_text,\n        });\n        if let Some(tid) = thread_id {\n            body[\"message_thread_id\"] = serde_json::Value::String(tid.to_string());\n        }\n\n        let resp = self\n            .client\n            .post(self.api_url(\"sendMessage\"))\n            .json(&body)\n            .send()\n            .await?;\n\n        if !resp.status().is_success() {\n            let err = resp.text().await.unwrap_or_default();\n            anyhow::bail!(\"Telegram sendMessage (draft) failed: {err}\");\n        }\n\n        let resp_json: serde_json::Value = resp.json().await?;\n        let message_id = resp_json\n            .get(\"result\")\n            .and_then(|r| r.get(\"message_id\"))\n            .and_then(|id| id.as_i64())\n            .map(|id| id.to_string());\n\n        self.last_draft_edit\n            .lock()\n            .insert(chat_id.to_string(), std::time::Instant::now());\n\n        Ok(message_id)\n    }\n\n    async fn update_draft(\n        &self,","sourceCodeStart":3845,"sourceCodeEnd":3881,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/telegram.rs#L3845-L3881","documentation":"send_draft() is the streaming-mode path: before streaming edits begin it posts the initial placeholder message ('...' when content is empty) as a plain JSON sendMessage and bails with Telegram's body (empty string if the body stream is unreadable) on non-2xx. The returned message_id is what later streaming edits target, so failure here aborts the whole streamed reply.","triggerScenarios":"403 'bot was blocked by the user' or chat not found — the draft is the first send of a reply, so chat-level problems surface here first; 429 flood control at reply start; 400 when recipient parsing produced a malformed chat_id; api_base/token problems.","commonSituations":"User blocked the bot right before a (streamed) reply fired; per-chat rate limit hit by back-to-back streamed replies; recipient string malformed after pairing data changes; wrong token after rotation.","solutions":["Read the embedded body — for streaming replies this is the first signal that the chat is undeliverable.","403 blocked/kicked: disable or drop that chat; delivery cannot resume until the user re-initiates.","429: honor retry_after, resend the draft, and pace subsequent edits.","Verify bot_token with getMe when drafts fail across all chats."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"let (chat_id, thread_id) = TelegramChannel::parse_reply_target(&message.recipient);\nanyhow::ensure!(!chat_id.is_empty(), \"recipient produced empty chat_id\");","typeGuard":"fn is_draft_chat_undeliverable(err: &anyhow::Error) -> bool {\n    let s = err.to_string();\n    s.contains(\"bot was blocked\") || s.contains(\"chat not found\") || s.contains(\"kicked\")\n}","tryCatchPattern":"if let Err(e) = telegram.send(message).await {\n    if e.to_string().contains(\"(draft) failed\") && is_draft_chat_undeliverable(&e) {\n        disable_chat(&message.recipient).await; // stop retrying dead chats\n        return Ok(());\n    }\n    return Err(e);\n}","preventionTips":["Treat draft-send 403s as a signal to prune the paired chat, since every streamed reply starts with this call.","Pace streamed replies to avoid 429 on the initial draft sendMessage.","Validate the parsed chat_id is non-empty before posting."],"tags":["telegram","sendmessage","streaming","draft","rate-limit"],"backgroundTag":"telegram-sendmessage-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}