zeroclaw-labs/zeroclaw · error

Telegram sendDocument failed: {err}

Error message

Telegram sendDocument failed: {err}

What it means

send_document() reads the file from disk and uploads it as a multipart sendDocument request (with optional caption and message_thread_id); any non-2xx answer bails with Telegram's response body embedded. The body's `description` is the authoritative reason.

Source

Thrown at crates/zeroclaw-channels/src/telegram.rs:3178

        if let Some(tid) = thread_id {
            form = form.text("message_thread_id", tid.to_string());
        }

        if let Some(cap) = caption {
            form = form.text("caption", cap.to_string());
        }

        let resp = self
            .http_client()
            .post(self.api_url("sendDocument"))
            .multipart(form)
            .send()
            .await?;

        if !resp.status().is_success() {
            let err = resp.text().await?;
            anyhow::bail!("Telegram sendDocument failed: {err}");
        }

        ::zeroclaw_log::record!(
            INFO,
            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)
                .with_attrs(::serde_json::json!({"chat_id": chat_id, "file_name": file_name})),
            "document sent to"
        );
        Ok(())
    }

    /// Send a document from bytes (in-memory) to a Telegram chat
    pub async fn send_document_bytes(
        &self,
        chat_id: &str,
        thread_id: Option<&str>,
        file_bytes: Vec<u8>,
        file_name: &str,

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Read the embedded body — `description` names the exact cause.
  2. Check the file size against the 50MB document cap and split or compress oversized files.
  3. Verify chat_id and, when used, that thread_id refers to an actual forum topic in that chat.
  4. On 429, honor retry_after and resend once.
Defensive patterns

Strategy: try-catch

Validate before calling

const DOC_LIMIT: u64 = 50 * 1024 * 1024;
let meta = tokio::fs::metadata(file_path).await?;
anyhow::ensure!(meta.len() <= DOC_LIMIT, "document {}MB exceeds 50MB", meta.len() / 1024 / 1024);
anyhow::ensure!(file_path.is_file(), "attachment is not a regular file");

Type guard

fn is_too_large(err: &anyhow::Error) -> bool {
    err.to_string().contains("413") || err.to_string().contains("file is too big")
}

Try / catch

if let Err(e) = channel.send_document(chat, thread, path, caption).await {
    if is_too_large(&e) {
        return channel.send_text_chunks("file too large to send", chat, thread).await;
    }
    return Err(e);
}

Prevention

When it happens

Trigger: 413 when the file exceeds Telegram's 50MB document limit; 400 'chat not found' for a bad chat_id or a thread_id that is not a forum topic; 403 blocked/kicked; 429 flood control; invalid multipart payload when the file disappears between the exists-check and tokio::fs::read.

Common situations: Exporting large logs/archives over 50MB; sending to a chat the bot was removed from; wrong thread_id for non-forum chats; rapid-fire document sends tripping limits.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/b4e72df172089895. Report an issue: GitHub.