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>
This commit is contained in:
Zeheng Huang 2026-07-01 04:26:27 -07:00 committed by Teknium
parent dc1ea005d9
commit 4c2c54c78c
3 changed files with 29 additions and 9 deletions

View file

@ -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()

View file

@ -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",

View file

@ -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()