Skip to content

Telegram MarkdownV2: bare URL with underscore triggers "Can't find end of a URL" (message truncated mid-link) #866

Description

@SidhayaPravda618

Summary

@chat-adapter/telegram (v4.29.0) silently truncates an outbound message and produces an invalid MarkdownV2 payload when the text contains a bare URL whose URL contains an unescaped MarkdownV2 entity marker (_, *, ~, or an odd number of backticks). Telegram then rejects the send with:

Bad Request: can't parse entities: Can't find end of a URL at byte offset N

Because it's a deterministic ValidationError, retries don't help and the message never gets delivered.

Root cause

  1. A bare URL such as https://example.com/org/org_abc123def/billing is turned into a link node by remark-gfm autolink-literal.

  2. renderMarkdownV2 renders it as a self-link: [escaped-label](raw-url). The label is escaped via escapeMarkdownV2 (so its _ becomes \_), but the URL in the (...) part goes through escapeLinkUrl, whose regex only escapes ) and \:

    var LINK_URL_SPECIAL_CHARS = /([)\\])/g;

    So the _ inside org_abc123def stays unescaped.

  3. That lone unescaped _ makes the whole message's count of unescaped _ odd.

  4. postMessage calls truncateForTelegram, which for MarkdownV2 runs trimToMarkdownV2SafeBoundary even when the text is well under the 4096 limit. That function sees the odd _ as an unpaired italic marker and truncates the message at that position — cutting [label](https://example.com/org/org with no closing ).

  5. Telegram receives a text-link whose URL never terminates → "Can't find end of a URL".

Minimal reproduction

import { TelegramFormatConverter } from '@chat-adapter/telegram';

const c = new TelegramFormatConverter();
const md = 'See billing: https://example.com/org/org_abc123def456ghi789jkl/billing';
console.log(JSON.stringify(c.fromMarkdown(md)));
// The (url) part contains an unescaped `_`. When postMessage runs
// truncateForTelegram -> trimToMarkdownV2SafeBoundary on this, the message
// is truncated at the `_`, leaving `[...](https://example.com/org/org` open,
// and Telegram sendMessage/editMessageText returns
//   Bad Request: can't parse entities: Can't find end of a URL

Any URL containing _, *, or ~ triggers it. A URL with _ (very common in tenant/org/resource IDs) reproduces every time.

Suggested fix

Make escapeLinkUrl escape the MarkdownV2 entity markers inside URLs so the unescaped-marker counting in trimToMarkdownV2SafeBoundary stays balanced (Telegram unescapes \_/\*/\~/\` back to the literal inside the (...) URL part, so the URL is preserved):

-var LINK_URL_SPECIAL_CHARS = /([)\\])/g;
+var LINK_URL_SPECIAL_CHARS = /([)\\*_~`])/g;

Alternatively, trimToMarkdownV2SafeBoundary could ignore markers that fall inside a link's (...) URL span. Either way, the "safe boundary" trim should never be able to cut a message in the middle of a [...](...) link.

Environment

  • @chat-adapter/telegram@4.29.0, chat@4.29.0
  • Node 20, parse mode MarkdownV2

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions