From 1b840329f2ef5334a80d91ef5f5b27d6432de9a3 Mon Sep 17 00:00:00 2001 From: Alexazhu Date: Sat, 18 Apr 2026 14:48:05 +0800 Subject: [PATCH] fix(bluebubbles): track fire-and-forget mark_read task to prevent GC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inbound webhook handler already stored its main handle_message task in self._background_tasks (with a done-callback that discards on completion), but the adjacent mark_read read-receipt task was scheduled with a bare asyncio.create_task(...) and the return value discarded. Python's event loop only holds a weak reference to tasks returned by create_task; the docs explicitly warn that untracked tasks can be garbage-collected before completing. In the webhook-receipt path this means a GC pass between task creation and the first await inside mark_read silently drops the read receipt — users see their messages as "unread" on the iMessage side even though the agent processed them. Route the task through the existing _background_tasks set with the same done-callback pattern used for handle_message five lines up. --- gateway/platforms/bluebubbles.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gateway/platforms/bluebubbles.py b/gateway/platforms/bluebubbles.py index a8a2929698..638e1bd82b 100644 --- a/gateway/platforms/bluebubbles.py +++ b/gateway/platforms/bluebubbles.py @@ -910,9 +910,14 @@ class BlueBubblesAdapter(BasePlatformAdapter): self._background_tasks.add(task) task.add_done_callback(self._background_tasks.discard) - # Fire-and-forget read receipt + # Fire-and-forget read receipt — tracked in _background_tasks so + # the event loop's weak-reference tracking of asyncio.create_task() + # does not garbage-collect it before the receipt reaches the + # BlueBubbles server (Python docs warn about this pattern). if self.send_read_receipts and session_chat_id: - asyncio.create_task(self.mark_read(session_chat_id)) + mark_read_task = asyncio.create_task(self.mark_read(session_chat_id)) + self._background_tasks.add(mark_read_task) + mark_read_task.add_done_callback(self._background_tasks.discard) return web.Response(text="ok")