From 4a3b44bc870905707920941fd40f961b6d3c699d Mon Sep 17 00:00:00 2001 From: jjbrunton Date: Fri, 22 May 2026 21:09:38 +0100 Subject: [PATCH] Don't crash bootstrap on Matrix token capture failure If chat.reddit.com sniffing fails, fall through with a placeholder credential set so the post-monitoring/processor side of the bot (and the web UI) stay up. Chat features are disabled until the next successful token refresh. Also dump diagnostics on sniff timeout: final page URL, every host the page touched (with request counts + whether any carried an Authorization header), and a full-page screenshot to data/matrix-debug.png. This makes the three failure modes (page didn't load / loaded but never hit Matrix / hit Matrix without auth) distinguishable from logs. Co-Authored-By: Claude Opus 4.7 --- src/bootstrap.ts | 20 +++++++++- src/services/chat/matrix/token-extractor.ts | 43 +++++++++++++++++++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/bootstrap.ts b/src/bootstrap.ts index c486055..c92352f 100644 --- a/src/bootstrap.ts +++ b/src/bootstrap.ts @@ -147,10 +147,28 @@ export async function bootstrap(config: Config, logger: Logger): Promise(); const onRequest = (request: import('playwright').Request) => { if (done) return; try { const url = new URL(request.url()); - if (!MATRIX_HOST_RE.test(url.hostname)) return; + const isMatrix = MATRIX_HOST_RE.test(url.hostname); const headers = request.headers(); const auth = headers['authorization'] || headers['Authorization']; - if (!auth || !auth.toLowerCase().startsWith('bearer ')) return; - const token = auth.slice('bearer '.length).trim(); + const hasAuth = !!auth && auth.toLowerCase().startsWith('bearer '); + const entry = seenHosts.get(url.hostname) ?? { count: 0, sawAuth: false }; + entry.count += 1; + if (hasAuth) entry.sawAuth = true; + seenHosts.set(url.hostname, entry); + if (!isMatrix) return; + if (!hasAuth) return; + const token = auth!.slice('bearer '.length).trim(); if (!token) return; const homeserver = `${url.protocol}//${url.host}`; done = true; @@ -105,7 +115,32 @@ export async function extractMatrixCredentials( ), ); - const captured = await Promise.race([credsPromise, timeout]); + let captured: MatrixCredentials; + try { + captured = await Promise.race([credsPromise, timeout]); + } catch (err) { + // On timeout, surface enough info to tell the three failure modes apart: + // (a) chat.reddit.com never loaded → page.url() still about:blank, almost no hosts + // (b) page loaded, no Matrix host hit → reddit hosts present, no matrix.* + // (c) Matrix hit, but no Authorization → matrix.* present with sawAuth: false + try { + const finalUrl = page.url(); + const hostSummary = Array.from(seenHosts.entries()) + .map(([host, info]) => `${host}=${info.count}${info.sawAuth ? '*' : ''}`) + .join(', '); + logger.error( + `[matrix] timeout diagnostics: finalUrl=${finalUrl} hosts=[${hostSummary || 'none'}]`, + ); + const shotDir = path.dirname(cachePath); + if (!fs.existsSync(shotDir)) fs.mkdirSync(shotDir, { recursive: true }); + const shotPath = path.join(shotDir, 'matrix-debug.png'); + await page.screenshot({ path: shotPath, fullPage: true }).catch(() => undefined); + logger.error(`[matrix] saved debug screenshot to ${shotPath}`); + } catch (diagErr) { + logger.warn('[matrix] failed to collect timeout diagnostics:', diagErr); + } + throw err; + } // Resolve the user_id by calling /whoami so the caller knows who we are. const whoamiRes = await fetchWithTimeout(