{"record":{"id":"d39fe2cc6f16d535","repo":"zeroclaw-labs/zeroclaw","slug":"telegram-sendmessage-approval-failed-status","errorCode":null,"errorMessage":"Telegram sendMessage (approval) failed ({status}): {err}","messagePattern":"Telegram sendMessage \\(approval\\) failed \\((.+?)\\): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/telegram.rs","lineNumber":4683,"sourceCode":"                });\n                if let Some(tid) = thread_id {\n                    plain_body[\"message_thread_id\"] = serde_json::Value::String(tid.to_string());\n                }\n\n                let plain_resp = self\n                    .http_client()\n                    .post(self.api_url(\"sendMessage\"))\n                    .json(&plain_body)\n                    .send()\n                    .await;\n\n                match plain_resp {\n                    Ok(r) if r.status().is_success() => true,\n                    Ok(r) => {\n                        let status = r.status();\n                        let err = r.text().await.unwrap_or_default();\n                        self.pending_approvals.lock().await.remove(&approval_id);\n                        anyhow::bail!(\"Telegram sendMessage (approval) failed ({status}): {err}\");\n                    }\n                    Err(e) => {\n                        self.pending_approvals.lock().await.remove(&approval_id);\n                        return Err(e.into());\n                    }\n                }\n            }\n            Err(e) => {\n                self.pending_approvals.lock().await.remove(&approval_id);\n                return Err(e.into());\n            }\n        };\n\n        if !send_ok {\n            self.pending_approvals.lock().await.remove(&approval_id);\n            anyhow::bail!(\"Telegram sendMessage (approval) failed after fallback\");\n        }\n","sourceCodeStart":4665,"sourceCodeEnd":4701,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/telegram.rs#L4665-L4701","documentation":"Raised by the Telegram channel's tool-approval flow when the fallback plain-text sendMessage to the Telegram Bot API returns a non-2xx status. The first send attempt (HTML parse_mode) already failed, the code retried without parse_mode keeping the inline approve/deny buttons, and that retry also failed. The pending approval entry is removed from the map before bailing, so the tool call that requested approval fails without ever getting an operator verdict.","triggerScenarios":"POST to api.telegram.org/bot<token>/sendMessage for an approval prompt returns non-success on both the HTML attempt and the plain-text fallback: 401 Unauthorized (bad or revoked bot token), 400 Bad Request (unknown chat_id, invalid message_thread_id, or user never pressed Start / blocked the bot), 404 (malformed token shape), 429 (rate limit hit).","commonSituations":"Bot token regenerated via BotFather so the configured channels.telegram token is now revoked; wrong chat_id or approval chat config; user blocked the bot or never initiated the chat; message_thread_id pointing at a deleted forum topic; bursts of simultaneous approval prompts hitting Telegram rate limits.","solutions":["Read the {status} in the message: 401/404 means the bot token is wrong or revoked — update channels.telegram bot token and verify with a getMe call","400 means Telegram rejects the request — verify chat_id is correct, the user has pressed Start on the bot, and message_thread_id (if set) refers to an existing topic","429 means rate limited — reduce approval volume and honor the retry_after value Telegram returns in {err}","Check the preceding WARN log 'Telegram sendMessage (approval) with HTML failed; retrying without parse_mode' — its status/err attrs show the first failure and tell you whether HTML markup or delivery itself was the problem","If a custom API base URL or proxy is configured for Telegram, verify it reaches the real Bot API"],"exampleFix":"# before: token revoked by regenerating in BotFather\n# zeroclaw.toml: channels.telegram.bot_token = \"123456:AA-old-revoked-token\"\n\ncurl -s \"https://api.telegram.org/bot<NEW_TOKEN>/getMe\"\n# {\"ok\":true,\"result\":{\"is_bot\":true,\"first_name\":\"...\"}}\n\n# after\n# zeroclaw.toml: channels.telegram.bot_token = \"123456:AA-new-valid-token\"","handlingStrategy":"try-catch","validationCode":"// Startup health check: verify the bot token can talk to Telegram\nasync fn telegram_token_ok(http: &reqwest::Client, token: &str) -> bool {\n    let url = format!(\"https://api.telegram.org/bot{token}/getMe\");\n    http.post(&url).send().await\n        .map(|r| r.status().is_success())\n        .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match approval_request.await {\n    Ok(resp) => { /* proceed */ }\n    Err(e) => {\n        let msg = e.to_string();\n        if msg.contains(\"401\") || msg.contains(\"404\") {\n            // fatal config problem: alert, do not retry\n            tracing::error!(\"telegram bot token invalid: {msg}\");\n        } else if msg.contains(\"429\") {\n            // rate limited: back off and re-request approval later\n            tokio::time::sleep(retry_after).await;\n        } else {\n            // treat as delivery failure: fall back to deny or another channel\n        }\n    }\n}","preventionTips":["Run a getMe health check at startup and fail fast on 401 instead of at first approval","Ensure the approval chat_id belongs to a user who has pressed Start on the bot","Watch for the WARN log 'sendMessage (approval) with HTML failed' — it precedes this error and carries the first failure's status","Honor Telegram retry_after on 429; serialize approval prompts instead of bursting"],"tags":["telegram","bot-api","http-status","approval","config"],"backgroundTag":"telegram-bot-api-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}