From 65dace1b1a06280f3bf4c36fa8b7fa52bf19b5c0 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 25 Mar 2026 11:28:28 -0700 Subject: [PATCH] fix(discord): stop phantom typing indicator after agent turn completes (#3003) Two fixes for a race where Discord's typing indicator lingers after the agent finishes: 1. _keep_typing (root cause): after outer stop_typing() clears the task dict, _keep_typing wakes from its 2s sleep and calls send_typing() again, recreating an orphaned loop. Add a finally block so _keep_typing always calls stop_typing() on exit, cleaning up any loop it recreated. 2. _process_message_background (safety net): add stop_typing() after cancelling the typing task, catching any platform-level persistent typing tasks that slipped through. Combines fixes from PR #2945 by catbusconductor (root cause in _keep_typing) and PR #2832 by subrih (safety net in _process_message_background). --- gateway/platforms/base.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/gateway/platforms/base.py b/gateway/platforms/base.py index a1c21c756..1d04176c2 100644 --- a/gateway/platforms/base.py +++ b/gateway/platforms/base.py @@ -819,6 +819,16 @@ class BasePlatformAdapter(ABC): await asyncio.sleep(interval) except asyncio.CancelledError: pass # Normal cancellation when handler completes + finally: + # Ensure the underlying platform typing loop is stopped. + # _keep_typing may have called send_typing() after an outer + # stop_typing() cleared the task dict, recreating the loop. + # Cancelling _keep_typing alone won't clean that up. + if hasattr(self, "stop_typing"): + try: + await self.stop_typing(chat_id) + except Exception: + pass async def handle_message(self, event: MessageEvent) -> None: """ @@ -1130,6 +1140,13 @@ class BasePlatformAdapter(ABC): await typing_task except asyncio.CancelledError: pass + # Also cancel any platform-level persistent typing tasks (e.g. Discord) + # that may have been recreated by _keep_typing after the last stop_typing() + try: + if hasattr(self, "stop_typing"): + await self.stop_typing(event.source.chat_id) + except Exception: + pass # Clean up session tracking if session_key in self._active_sessions: del self._active_sessions[session_key]