Skip to content

Telegram reply dispatcher silently swallows delivery errors #15772

@odinmadeofstarstoo

Description

@odinmadeofstarstoo

Bug Report

Summary

The Telegram reply dispatcher (dispatchReplyWithBufferedBlockDispatcher) silently catches delivery errors via its onError callback, which logs to runtime.error() but does not re-throw or propagate the failure. This means messages can fail to deliver to Telegram without any indication to the user — the message appears on the dashboard/webchat but never reaches Telegram.

Observed Behavior

  1. Assistant generates a response (visible in session transcript with stopReason: "stop")
  2. The response appears on the OpenClaw dashboard
  3. The response never arrives on Telegram
  4. No error is surfaced to the user or the agent
  5. The session transcript contains no delivery error record

Expected Behavior

  • Delivery failures should be propagated or at least recorded in the session transcript
  • The agent should be notified that delivery failed so it can retry or use the message tool as fallback
  • Ideally, the dispatcher should retry delivery at least once before giving up

Root Cause (from source analysis)

In src/telegram/bot-message-dispatch.ts, the dispatchReplyWithBufferedBlockDispatcher is called with an onError handler that catches exceptions:

onError: (err, info) => {
    runtime.error?.(danger(`telegram ${info.kind} reply failed: ${String(err)}`));
},

This logs the error but does not re-throw, so the error is silently consumed. The deliveryState.delivered remains false, but the only fallback is sending EMPTY_RESPONSE_FALLBACK — which may also fail via the same silent path.

Likely Trigger

Formatted messages containing markdown (bold, backticks, em dashes, bullet points) that cause Telegram's HTML parser to reject the message. There is a fallback from HTML to plain text, but if both fail, the error is swallowed.

Additional Issue: No Retry on Inbound Media Fetch

fetchRemoteMedia (used for inbound Telegram media downloads from api.telegram.org/file/) also has zero retries. A single transient TCP failure (TypeError: fetch failed) results in permanent media loss for that message. We observed intermittent failures at multiple timestamps that resolved after a gateway restart, suggesting transient network/connection pool issues.

Suggested Fix

  1. Propagate errors: Record delivery failures in the session transcript so the agent can detect them
  2. Add retry: Retry delivery at least once (with a short delay) before giving up
  3. Notify agent: If delivery fails after retries, inject a system message so the agent can attempt redelivery via the message tool
  4. Add retry to fetchRemoteMedia: At least one retry with backoff for inbound media downloads
  5. Log persistently: Consider writing delivery failures to a log file, not just runtime.error()

Environment

  • OpenClaw: 2026.2.12
  • Channel: Telegram (polling mode, streamMode: "partial")
  • Node.js: v22.22.0
  • Grammy: 1.40.0
  • Undici: 7.21.0
  • OS: Ubuntu Linux x64

Metadata

Metadata

Assignees

No one assigned

    Labels

    staleMarked as stale due to inactivity

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions