fix(matrix): use a genuinely discriminating fixture for the sync-loop test

The independent-verifier caught that my first loop-level test did not
actually prove anything. The 502/SVG coordinate fixture I reused from
gmoranxyz's unit-level test does not contain the substring 403 once
case-folded, so the old naive substring classifier already treated it
as transient. A test that passes under both the buggy code and the
fix proves nothing about the fix.

I replaced the fixture with a plain connection timeout whose message
wraps the real Matrix sync pagination token, an arbitrary digit
string that happens to contain 401. I verified this directly: with
the pre-fix classifier restored, the retry test now fails (the old
code stops the loop on this fixture), and with the fix in place it
passes (the loop retries as it should). That is the RED/GREEN proof
the maintainer originally asked for.

I also documented in the stop test's docstring that it does not
discriminate old from new, since the word forbidden in its message
trips the old naive check too. It is still worth keeping as a
regression test proving genuine auth errors stop the loop, just not
as proof of this specific fix.

While I was in there I also fixed a stale comment above the
M_UNKNOWN_TOKEN sync-object pre-check. It said nio returns SyncError
objects, but the dependency here is mautrix, not matrix-nio, and
importing nio raises ModuleNotFoundError in this codebase. The
pre-check logic itself was already correct and untouched.

Co-authored-by: gmoranxyz <gmoranxyz@users.noreply.github.com>
(cherry picked from commit ad3aad579a675a5aae544a50f88a82717c0ac3b6)
This commit is contained in:
Stephen Chin
2026-08-05 23:22:59 -07:00
committed by kshitij
parent f8085f2e03
commit 6bd9cf8018
2 changed files with 56 additions and 13 deletions
+19
View File
@@ -1837,9 +1837,28 @@ class MatrixAdapter(BasePlatformAdapter):
next_batch = await client.sync_store.get_next_batch() # resume from the initial sync
while not self._closing:
try:
<<<<<<< HEAD
# 45s outer cap guards TCP-level hangs the 30s long-poll timeout can't catch.
sync_data = await asyncio.wait_for(client.sync(since=next_batch, timeout=30000), timeout=45.0)
# Auth failures (M_UNKNOWN_TOKEN) arrive as SyncError objects, not exceptions.
=======
# Wrap in asyncio.wait_for to guard against TCP-level hangs
# that the Matrix long-poll timeout cannot catch. Long-poll
# is 30s, so 45s gives 15s slack for network drain.
sync_data = await asyncio.wait_for(
client.sync(
since=next_batch,
timeout=30000,
),
timeout=45.0,
)
# mautrix's Client.sync() returns a plain dict on success but
# an object carrying a "message" string (not a raised
# exception) for auth failures like M_UNKNOWN_TOKEN. Detect
# and stop immediately rather than falling through to the
# dict-shaped handling below.
>>>>>>> 4e16313582 (fix(matrix): use a genuinely discriminating fixture for the sync-loop test)
_sync_msg = getattr(sync_data, "message", None)
if isinstance(_sync_msg, str) and "unknown_token" in _sync_msg.lower():
logger.error("Matrix: permanent auth error from sync: %s — stopping", _sync_msg)