[BUG] Telegram plugin: Claude's markdown syntax sent literally, not rendered

Resolved 💬 3 comments Opened Apr 13, 2026 by adamfarag Closed Apr 16, 2026

Summary

When the Telegram plugin's reply tool is called with format omitted (default 'text'), Claude's markdown-formatted text (e.g. **bold**, ` code , [link](url)`) is sent to Telegram as literal characters. Recipients see the asterisks/backticks/brackets instead of formatted output.

Current behavior

  • Claude tends to produce markdown in responses by default (per its training).
  • The plugin's default format='text' sends the string as-is.
  • Telegram with no parse_mode renders it as literal text, so users see **bold** instead of bold.

What I tested

  1. format='text' (default) — sends raw string, **bold** shown literally. ✗
  2. format='markdownv2' unescaped — e.g. "*bold* _italic_ code\\nPlain line with a period." → Telegram returns 400: Bad Request: can't parse entities: Character '.' is reserved and must be escaped with the preceding '\\'. Any unescaped reserved character in plain text kills the whole message. ✗
  3. format='markdownv2' with strict escaping — e.g. "*bold* _italic_ code ~strike~\\nPlain line with an escaped period\\\\." → renders correctly. ✓
  4. HTML mode — not exposed by current tool (enum is text | markdownv2). Would also work per Telegram's Bot API formatting docs.

Why the default breaks

MarkdownV2 mode requires backslash-escaping every occurrence of these characters outside of formatting spans: _ * [ ] ( ) ~ \ > # + - = | { } . !. In practice Claude's output almost always contains unescaped periods, parens, or dashes, so switching the default to markdownv2` without escaping is strictly worse.

Claude also uses GitHub-flavored markdown syntax (**bold**, *italic*), not MarkdownV2 (*bold*, _italic_), so there's a syntax mismatch even before escaping.

Suggested fix (concrete)

In the reply / send-message tool, intercept the text before calling sendMessage:

Path A — plain-text default (least risky):

  • When format is omitted or 'text', run a minimal markdown stripper: remove **/__ pairs, remove single */_/` `, convert [text](url)text (url) or text, strip > ` block quotes, etc.
  • This keeps messages readable without any parse_mode risk.

Path B — rich formatting by default:

  • Add a new format: 'auto' (and make it the default). The plugin:
  1. Converts Claude's GFM (**bold***bold*, *italic*_italic_, [t](u)[t](u), fenced `` to ``).
  2. Backslash-escapes every MarkdownV2 reserved char that sits outside a formatting span.
  3. Sends with parse_mode: MarkdownV2.
  • If conversion fails (malformed spans), fall back to Path A (stripped plain text) instead of returning an API error.

Path C — tool-description nudge (minimum viable):

  • Update the reply tool description to tell Claude: "Default mode sends plain text — do not use markdown syntax unless you also pass format: 'markdownv2' and escape reserved characters per Telegram MarkdownV2 rules."
  • Cheap, but relies on model compliance every turn.

Best combo is probably A by default + B behind format: 'auto'. Either is strictly better than current behavior where markdown leaks through as literal text.

Related

  • #36622 (closed as invalid) — requested MarkdownV2 support; this is a different angle (Claude's already-produced markdown silently breaks in plain-text mode).

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗