From 4c2c54c78c3731c3f78787b6c7882eb4aea378d7 Mon Sep 17 00:00:00 2001 From: Zeheng Huang <153708448+hunjaiboy@users.noreply.github.com> Date: Wed, 1 Jul 2026 04:26:27 -0700 Subject: [PATCH] fix(matrix): await inbound sync handlers Register the Matrix room-message, reaction, and invite handlers with mautrix's wait_sync=True. mautrix's handle_sync() only returns the tasks for handlers registered as sync-awaited; non-waited handlers are fire-and-forget via background_task.create() and are NOT returned. Since _dispatch_sync() awaits only the returned tasks (await asyncio.gather), the inbound handlers previously had no completion point, so Tuwunel/ mautrix homeservers connected and completed initial sync but dispatched zero inbound messages. Fixes #46142. Co-authored-by: Zeheng Huang <153708448+hunjaiboy@users.noreply.github.com> --- plugins/platforms/matrix/adapter.py | 18 +++++++++++++++--- scripts/release.py | 1 + tests/gateway/test_matrix.py | 19 +++++++++++++------ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/plugins/platforms/matrix/adapter.py b/plugins/platforms/matrix/adapter.py index 17e47d4244e..ebe9ebbbff8 100644 --- a/plugins/platforms/matrix/adapter.py +++ b/plugins/platforms/matrix/adapter.py @@ -1459,9 +1459,21 @@ class MatrixAdapter(BasePlatformAdapter): # Without this the INVITE handler below never fires. client.add_dispatcher(MembershipEventDispatcher) - client.add_event_handler(EventType.ROOM_MESSAGE, self._on_room_message) - client.add_event_handler(EventType.REACTION, self._on_reaction) - client.add_event_handler(IntEvt.INVITE, self._on_invite) + client.add_event_handler( + EventType.ROOM_MESSAGE, + self._on_room_message, + wait_sync=True, + ) + client.add_event_handler( + EventType.REACTION, + self._on_reaction, + wait_sync=True, + ) + client.add_event_handler( + IntEvt.INVITE, + self._on_invite, + wait_sync=True, + ) # Initial sync to catch up, then start background sync. self._startup_ts = time.time() diff --git a/scripts/release.py b/scripts/release.py index c42ee55e789..9220162ade0 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -232,6 +232,7 @@ AUTHOR_MAP = { "290859878+synapsesx@users.noreply.github.com": "synapsesx", "157689911+itsflownium@users.noreply.github.com": "itsflownium", "dirtyren@users.noreply.github.com": "dirtyren", + "153708448+hunjaiboy@users.noreply.github.com": "yyzquwu", # PR #47567 salvage (Matrix: register inbound handlers with wait_sync=True so _dispatch_sync's gather awaits them; without it mautrix fire-and-forgets and inbound intake has no completion point) "tgmerritt@gmail.com": "tgmerritt", # PR #43553 salvage (parse vLLM's token-based output-cap error format so over-cap max_tokens 400s reduce the output cap instead of death-looping into compression) "13277570+justin-cyhuang@users.noreply.github.com": "justin-cyhuang", "agent@tranquil-flow.dev": "Tranquil-Flow", diff --git a/tests/gateway/test_matrix.py b/tests/gateway/test_matrix.py index dd413c9f74a..7484220456b 100644 --- a/tests/gateway/test_matrix.py +++ b/tests/gateway/test_matrix.py @@ -106,7 +106,7 @@ def _make_fake_mautrix(): self.crypto = None self._event_handlers = {} - def add_event_handler(self, event_type, handler): + def add_event_handler(self, event_type, handler, **kwargs): self._event_handlers.setdefault(event_type, []).append(handler) def add_dispatcher(self, dispatcher_type): @@ -2727,13 +2727,20 @@ class TestMatrixEncryptedEventHandler: with patch.object(adapter, "_sync_loop", AsyncMock(return_value=None)): assert await adapter.connect() is True - # Verify event handlers were registered. - # In mautrix the order is: add_event_handler(EventType, callback) + # Verify inbound event handlers were registered as sync-awaited + # callbacks. mautrix only returns waited handler tasks from + # handle_sync(), so background-only handlers leave _dispatch_sync() + # without a completion point for Hermes' Matrix intake. handler_calls = mock_client.add_event_handler.call_args_list - registered_types = [call.args[0] for call in handler_calls] + waited_types = { + str(call.args[0]) + for call in handler_calls + if call.kwargs.get("wait_sync") is True + } - # Should have registered handlers for ROOM_MESSAGE, REACTION, INVITE - assert len(handler_calls) >= 3 + assert "m.room.message" in waited_types + assert "m.reaction" in waited_types + assert "internal.invite" in waited_types await adapter.disconnect()