From be0728cacc5e3c8edc7a2ca93908ceb136da101b Mon Sep 17 00:00:00 2001 From: Omar B Date: Sat, 9 May 2026 04:17:53 -0400 Subject: [PATCH] fix: handle Discord typing indicator 429 gracefully MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The typing indicator loop (send_typing) ran every 8s and died on any exception, including Discord 429 rate limits. Once a 429 killed the loop, the indicator never restarted — and the raw exception bounce could cascade into broader gateway instability. Changes: - Bump sleep interval from 8s to 12s (typing light lasts ~10s) - On 429: extract retry_after, log a warning, sleep the backoff, and continue the loop - On non-rate-limit errors: log debug and return (unchanged behaviour) --- gateway/platforms/discord.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/gateway/platforms/discord.py b/gateway/platforms/discord.py index 32a0026973a..0d64b24d7e4 100644 --- a/gateway/platforms/discord.py +++ b/gateway/platforms/discord.py @@ -2706,8 +2706,13 @@ class DiscordAdapter(BasePlatformAdapter): Discord's TYPING_START gateway event is unreliable in DMs for bots. Instead, start a background loop that hits the typing endpoint every - 8 seconds (typing indicator lasts ~10s). The loop is cancelled when + 12 seconds (typing indicator lasts ~10s). The loop is cancelled when stop_typing() is called (after the response is sent). + + Rate-limit handling: if a 429 is encountered, the loop logs a + warning, sleeps for the ``retry_after`` duration (or a sensible + default), and continues — it does NOT die on a single rate-limit + hit. Only CancelledError (from stop_typing) stops the loop. """ if not self._client: return @@ -2727,9 +2732,22 @@ class DiscordAdapter(BasePlatformAdapter): except asyncio.CancelledError: return except Exception as e: - logger.debug("Discord typing indicator failed for %s: %s", chat_id, e) - return - await asyncio.sleep(8) + # Don't die on 429 — backoff and continue + retry_after = self._extract_discord_retry_after(e) + if retry_after is not None: + logger.warning( + "Typing indicator rate-limited for %s; retrying in %.1fs", + chat_id, retry_after, + ) + else: + logger.debug( + "Discord typing indicator failed for %s: %s", + chat_id, e, + ) + return + await asyncio.sleep(retry_after) + continue + await asyncio.sleep(12) except asyncio.CancelledError: pass finally: