From 78c7e22839f101e731d82b35276c3653c1aabcc8 Mon Sep 17 00:00:00 2001 From: Hermine Date: Tue, 9 Jun 2026 00:38:55 +0200 Subject: [PATCH] fix(telegram): always apply MarkdownV2 formatting on edit_message Previously, edit_message(finalize=False) sent content as plain text without parse_mode=MarkdownV2, while edit_message(finalize=True) and send() both applied MarkdownV2 formatting. This caused tool progress edits to replace a correctly formatted message with raw markdown text, breaking code blocks, headers, bold, and other formatting in the Telegram chat. The fix always applies format_message() + parse_mode=MARKDOWN_V2 regardless of the finalize flag, with the same fallback to plain text on parse errors. Added logging for the fallback case (previously silent). Fixes: #25710 (related) --- gateway/platforms/telegram.py | 31 +++++++++++++++++++------------ gateway/run.py | 5 ++++- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/gateway/platforms/telegram.py b/gateway/platforms/telegram.py index b97d430d4a44..549450d5246f 100644 --- a/gateway/platforms/telegram.py +++ b/gateway/platforms/telegram.py @@ -2188,14 +2188,11 @@ async def edit_message( ) try: - if not finalize: - await self._bot.edit_message_text( - chat_id=int(chat_id), - message_id=int(message_id), - text=content, - ) - return SendResult(success=True, message_id=message_id) - + # Always apply MarkdownV2 formatting on edit, even for + # intermediate (finalize=False) updates. Without this, a + # progress edit that follows an initial formatted send() + # replaces the MarkdownV2-rendered message with raw plain + # text — breaking code blocks, bold, headers, etc. formatted = self.format_message(content) try: await self._bot.edit_message_text( @@ -2209,6 +2206,10 @@ async def edit_message( if "not modified" in str(fmt_err).lower(): return SendResult(success=True, message_id=message_id) # Fallback: retry without markdown formatting + logger.warning( + "[%s] MarkdownV2 edit failed, falling back to plain text: %s", + self.name, fmt_err, + ) await self._bot.edit_message_text( chat_id=int(chat_id), message_id=int(message_id), @@ -4369,7 +4370,8 @@ def _ph(value: str) -> str: text = _wrap_markdown_tables(text) # 1) Protect fenced code blocks (``` ... ```) - # Per MarkdownV2 spec, \ and ` inside pre/code must be escaped. + # Per MarkdownV2 spec, these characters must be escaped inside + # pre/code blocks too: \ ` * _ { } [ ] ( ) # + - . ! > def _protect_fenced(m): raw = m.group(0) # Split off opening ``` (with optional language) and closing ``` @@ -4377,7 +4379,7 @@ def _protect_fenced(m): opening = raw[:open_end] body_and_close = raw[open_end:] body = body_and_close[:-3] - body = body.replace('\\', '\\\\').replace('`', '\\`') + body = _escape_mdv2(body) return _ph(opening + body + '```') text = re.sub( @@ -4387,10 +4389,15 @@ def _protect_fenced(m): ) # 2) Protect inline code (`...`) - # Escape \ inside inline code per MarkdownV2 spec. + # Per MarkdownV2 spec, all special chars must be escaped inside code. + def _protect_inline(m): + inner = m.group(0)[1:-1] # strip outer backticks + escaped = _escape_mdv2(inner) + return _ph(f'`{escaped}`') + text = re.sub( r'(`[^`]+`)', - lambda m: _ph(m.group(0).replace('\\', '\\\\')), + _protect_inline, text, ) diff --git a/gateway/run.py b/gateway/run.py index d5b39c31df11..1b018207f1de 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -12990,7 +12990,10 @@ def progress_callback(event_type: str, tool_name: str = None, preview: str = Non and isinstance(args.get("command"), str) and args["command"].strip() ): - _bash_block = f"```bash\n{args['command'].rstrip()}\n```" + # Escape triple-backticks inside the command so they don't + # break the fenced code-block boundary that wraps them. + _cmd = args['command'].rstrip().replace('```', '\\`\\`\\`') + _bash_block = f"```bash\n{_cmd}\n```" # Verbose mode: show detailed arguments, respects tool_preview_length if progress_mode == "verbose":