Skip to content

fix(telegram): escape MarkdownV2 reserved parens in chunk indicators (#74004) - #74040

Open
Tranquil-Flow wants to merge 1 commit into
NousResearch:mainfrom
Tranquil-Flow:fix/74004-tg-mdv2-chunk-indicator
Open

fix(telegram): escape MarkdownV2 reserved parens in chunk indicators (#74004)#74040
Tranquil-Flow wants to merge 1 commit into
NousResearch:mainfrom
Tranquil-Flow:fix/74004-tg-mdv2-chunk-indicator

Conversation

@Tranquil-Flow

Copy link
Copy Markdown
Contributor

What

When _send_telegram() (the standalone send path used by cron delivery when
the gateway is down) chunks a long MarkdownV2 message, truncate_message()
appends a raw (1/N) indicator after format_message() has already
escaped all MarkdownV2 special characters. The unescaped ( and ) cause
Telegram to reject the parse and silently fall back to plain text, stripping
all formatting.

The live gateway adapter (plugins/platforms/telegram/adapter.py) already
applies this exact transformation; the standalone path was missing it — a
classic code-duplication gap.

How

After truncate_message(), when send_parse_mode == ParseMode.MARKDOWN_V2
and the message spans multiple chunks, apply both transformations the live
adapter uses:

  1. re.sub(r" \((\d+)/(\d+)\)$", r" \\\(\1/\2\\\)", chunk) — escape the
    indicator parens.
  2. _separate_chunk_indicator_from_fence(chunk) — move the indicator off a
    trailing code-fence line so Telegram treats the fence as a clean close.

This reuses the existing _separate_chunk_indicator_from_fence helper (already
used in 3 other call sites in the adapter) rather than duplicating the logic.

HTML mode is exempt: the guard is scoped to MARKDOWN_V2, so parens stay
unescaped when parse_mode='HTML'.

Why layer 2 matters

When truncate_message() splits inside a fenced code block, it closes the
fence and appends (1/N) on the same line as the closing fence. Telegram
does not treat that as a clean fence close and again falls back to plain text.

Tests

4 new production-path regression tests (RED on upstream/main, GREEN with fix):

  • test_chunk_indicators_have_escaped_parens — multi-chunk MDv2 message has escaped (N/M) indicators (RED proof: raw (1/3) parens reached unescaped)
  • test_single_chunk_no_indicator — short message gets no indicator
  • test_html_mode_chunked_no_paren_escaping — HTML mode does NOT backslash-escape parens
  • test_chunked_code_fence_indicator_separated — indicator not glued to closing fence

Results: 4/4 new tests pass; 8/8 nearby test_telegram_send_message_* pass.
git rev-list --left-right --count upstream/main...HEAD = 0 1.

Competitor

PR #74028 (kyssta-exe) addresses the same root cause but is incomplete: no
committed regression test, missing the code-fence-separation layer (Layer 2),
and uses a redundant inline import re as _re when re is already imported at
module level.

Fixes #74004.

Auto-published by Moonsong via Path B automated pipeline.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery platform/telegram Telegram bot adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jul 29, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: #74028 escapes the same standalone MarkdownV2 chunk-indicator parentheses. This PR also applies the existing fence-separation helper for the distinct case where a generated indicator shares a closing code-fence line; reviewers should compare the broader coverage before choosing a consolidation path.

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for adding standalone-path parity and regression coverage. Current main still sends raw (N/M) suffixes from _send_telegram() at tools/send_message_tool.py:1297-1305, while BasePlatformAdapter.truncate_message() adds those suffixes at gateway/platforms/base.py:6828-6833. The proposed transformation matches the live Telegram adapter's escape-and-fence-separation sequence at plugins/platforms/telegram/adapter.py:4368-4377, including the helper documented at plugins/platforms/telegram/adapter.py:470-480.

Automated hermes-sweeper review.

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

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/telegram Telegram bot adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Telegram Markdown formatting silently lost on chunked messages in standalone send path (_send_telegram)

3 participants