{"repo":"NousResearch/hermes-agent","public_id":"1526","display_label":"Retry logic for transient startup and TLS failures","summary":"These members address missing or insufficient retry behavior when transient TLS errors or other retryable failures occur during gateway startup and Telegram connection/send operations. Both the gateway restart and Telegram-specific TLS retry cases are represented.","subsystem":"gateway","counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":1535,"merged_at":"2026-03-16T12:28:12+00:00"},"resolution":{"state":"resolved","by":"pr-1535"},"freshness":{"state":"stale","last_verified_at":"2026-03-16T12:28:12+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":1526,"pr_number":1535,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-03-16T12:28:12+00:00"}],"members":[{"kind":"issue","number":1526,"title":"[Bug]: Telegram connection fails intermittently on startup due to transient TLS reset with no retry","url":null,"state":"closed"},{"kind":"pr","number":1527,"title":"fix(telegram): retry on transient TLS failures during connect and send","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#1535","number":1535}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":1535,"title":"fix(telegram): retry on transient TLS failures during connect and send","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["gateway/platforms/telegram.py"],"fix_hunks":[{"file":"gateway/platforms/telegram.py","hunks":["@@ -202,8 +202,26 @@ async def connect(self) -> bool:\n                 self._handle_media_message\n             ))\n             \n-            # Start polling in background\n-            await self._app.initialize()\n+            # Start polling — retry initialize() for transient TLS resets\n+            try:\n+                from telegram.error import NetworkError, TimedOut\n+            except ImportError:\n+                NetworkError = TimedOut = OSError  # type: ignore[misc,assignment]\n+            _max_connect = 3\n+            for _attempt in range(_max_connect):\n+                try:\n+                    await self._app.initialize()\n+                    break\n+                except (NetworkError, TimedOut, OSError) as init_err:\n+                    if _attempt < _max_connect - 1:\n+                        wait = 2 ** _attempt\n+                        logger.warning(\n+                            \"[%s] Connect attempt %d/%d failed: %s — retrying in %ds\",\n+                            self.name, _attempt + 1, _max_connect, init_err, wait,\n+                        )\n+                        await asyncio.sleep(wait)\n+                    else:\n+                        raise\n             await self._app.start()\n             loop = asyncio.get_running_loop()\n ","@@ -334,32 +352,47 @@ async def send(\n             message_ids = []\n             thread_id = metadata.get(\"thread_id\") if metadata else None\n             \n+            try:\n+                from telegram.error import NetworkError as _NetErr\n+            except ImportError:\n+                _NetErr = OSError  # type: ignore[misc,assignment]\n+\n             for i, chunk in enumerate(chunks):\n-                # Try Markdown first, fall back to plain text if it fails\n-                try:\n-                    msg = await self._bot.send_message(\n-                        chat_id=int(chat_id),\n-                        text=chunk,\n-                        parse_mode=ParseMode.MARKDOWN_V2,\n-                        reply_to_message_id=int(reply_to) if reply_to and i == 0 else None,\n-                        message_thread_id=int(thread_id) if thread_id else None,\n-                    )\n-                except Exception as md_error:\n-                    # Markdown parsing failed, try plain text\n-                    if \"parse\" in str(md_error).lower() or \"markdown\" in str(md_error).lower():\n-                        logger.warning(\"[%s] MarkdownV2 parse failed, falling back to plain text: %s\", self.name, md_error)\n-                        # Strip MDV2 escape backslashes so the user doesn't\n-                        # see raw backslashes littered through the message.\n-                        plain_chunk = _strip_mdv2(chunk)\n-                        msg = await self._bot.send_message(\n-                            chat_id=int(chat_id),\n-                            text=plain_chunk,\n-                            parse_mode=None,  # Plain text\n-                            reply_to_message_id=int(reply_to) if reply_to and i == 0 else None,\n-                            message_thread_id=int(thread_id) if thread_id else None,\n-                        )\n-                    else:\n-                        raise  # Re-raise if not a parse error\n+                msg = None\n+                for _send_attempt in range(3):\n+                    try:\n+                        # Try Markdown first, fall back to plain text if it fails\n+                        try:\n+                            msg = await self._bot.send_message(\n+                                chat_id=int(chat_id),\n+                                text=chunk,\n+                                parse_mode=ParseMode.MARKDOWN_V2,\n+                                reply_to_message_id=int(reply_to) if reply_to and i == 0 else None,\n+                                message_thread_id=int(thread_id) if thread_id else None,\n+                            )\n+                        except Exception as md_error:\n+                            # Markdown parsing failed, try plain text\n+                            if \"parse\" in str(md_error).lower() or \"markdown\" in str(md_error).lower():\n+                                logger.warning(\"[%s] MarkdownV2 parse failed, falling back to plain text: %s\", self.name, md_error)\n+                                plain_chunk = _strip_mdv2(chunk)\n+                                msg = await self._bot.send_message(\n+                                    chat_id=int(chat_id),\n+                                    text=plain_chunk,\n+                                    parse_mode=None,\n+                                    reply_to_message_id=int(reply_to) if reply_to and i == 0 else None,\n+                                    message_thread_id=int(thread_id) if thread_id else None,\n+                                )\n+                            else:\n+                                raise\n+                        break  # success\n+                    except _NetErr as send_err:\n+                        if _send_attempt < 2:\n+                            wait = 2 ** _send_attempt\n+                            logger.warning(\"[%s] Network error on send (attempt %d/3), retrying in %ds: %s\",\n+                                           self.name, _send_attempt + 1, wait, send_err)\n+                            await asyncio.sleep(wait)\n+                        else:\n+                            raise\n                 message_ids.append(str(msg.message_id))\n             \n             return SendResult(\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}