{"repo":"NousResearch/hermes-agent","public_id":"46142","display_label":"Matrix inbound sync handler awaiting and dispatch issue","summary":"Two items share the title \"fix(matrix): await inbound sync handlers\", and a third is a bug report about inbound message dispatch on a Tuwunel homeserver after a mautrix migration (PR #18505). The titles suggest a shared Matrix inbound handling theme, but the available information does not confirm that the fixes address the reported bug.","subsystem":"gateway","counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":56345,"merged_at":"2026-07-01T11:42:33+00:00"},"resolution":{"state":"resolved","by":"pr-56345"},"freshness":{"state":"verified","last_verified_at":"2026-07-01T11:42:33+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":46142,"pr_number":56345,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-07-01T11:42:33+00:00"}],"members":[{"kind":"issue","number":46142,"title":"[Bug]: Matrix gateway — mautrix migration (PR #18505) breaks inbound message dispatch on Tuwunel homeserver","url":null,"state":"closed"},{"kind":"pr","number":47567,"title":"fix(matrix): await inbound sync handlers","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"4c2c54c78","number":0},{"kind":"pr","value":"#56345","number":56345}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":56345,"title":"fix(matrix): await inbound sync handlers","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["plugins/platforms/matrix/adapter.py","scripts/release.py","tests/gateway/test_matrix.py"],"fix_hunks":[{"file":"plugins/platforms/matrix/adapter.py","hunks":["@@ -1459,9 +1459,21 @@ async def connect(self, *, is_reconnect: bool = False) -> bool:\n         # Without this the INVITE handler below never fires.\n         client.add_dispatcher(MembershipEventDispatcher)\n \n-        client.add_event_handler(EventType.ROOM_MESSAGE, self._on_room_message)\n-        client.add_event_handler(EventType.REACTION, self._on_reaction)\n-        client.add_event_handler(IntEvt.INVITE, self._on_invite)\n+        client.add_event_handler(\n+            EventType.ROOM_MESSAGE,\n+            self._on_room_message,\n+            wait_sync=True,\n+        )\n+        client.add_event_handler(\n+            EventType.REACTION,\n+            self._on_reaction,\n+            wait_sync=True,\n+        )\n+        client.add_event_handler(\n+            IntEvt.INVITE,\n+            self._on_invite,\n+            wait_sync=True,\n+        )\n \n         # Initial sync to catch up, then start background sync.\n         self._startup_ts = time.time()"]},{"file":"scripts/release.py","hunks":["@@ -232,6 +232,7 @@\n     \"290859878+synapsesx@users.noreply.github.com\": \"synapsesx\",\n     \"157689911+itsflownium@users.noreply.github.com\": \"itsflownium\",\n     \"dirtyren@users.noreply.github.com\": \"dirtyren\",\n+    \"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)\n     \"[redacted:email]\": \"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)\n     \"13277570+justin-cyhuang@users.noreply.github.com\": \"justin-cyhuang\",\n     \"[redacted:email]\": \"Tranquil-Flow\","]},{"file":"tests/gateway/test_matrix.py","hunks":["@@ -106,7 +106,7 @@ def __init__(self, mxid=None, device_id=None, api=None,\n             self.crypto = None\n             self._event_handlers = {}\n \n-        def add_event_handler(self, event_type, handler):\n+        def add_event_handler(self, event_type, handler, **kwargs):\n             self._event_handlers.setdefault(event_type, []).append(handler)\n \n         def add_dispatcher(self, dispatcher_type):","@@ -2727,13 +2727,20 @@ async def test_connect_registers_encrypted_event_handler_when_encryption_on(self\n                     with patch.object(adapter, \"_sync_loop\", AsyncMock(return_value=None)):\n                         assert await adapter.connect() is True\n \n-        # Verify event handlers were registered.\n-        # In mautrix the order is: add_event_handler(EventType, callback)\n+        # Verify inbound event handlers were registered as sync-awaited\n+        # callbacks. mautrix only returns waited handler tasks from\n+        # handle_sync(), so background-only handlers leave _dispatch_sync()\n+        # without a completion point for Hermes' Matrix intake.\n         handler_calls = mock_client.add_event_handler.call_args_list\n-        registered_types = [call.args[0] for call in handler_calls]\n+        waited_types = {\n+            str(call.args[0])\n+            for call in handler_calls\n+            if call.kwargs.get(\"wait_sync\") is True\n+        }\n \n-        # Should have registered handlers for ROOM_MESSAGE, REACTION, INVITE\n-        assert len(handler_calls) >= 3\n+        assert \"m.room.message\" in waited_types\n+        assert \"m.reaction\" in waited_types\n+        assert \"internal.invite\" in waited_types\n \n         await adapter.disconnect()\n \n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}