From d87c4e99e6c3590cfb639b64803c2855efa75736 Mon Sep 17 00:00:00 2001 From: glass beadian Date: Wed, 14 Oct 2026 13:55:05 +0000 Subject: [PATCH] fix: provisioner matrix login retries through M_LIMIT_EXCEEDED instead of failing boot Rapid deploy restarts exhausted the homeserver's per-account login rate limit; bootstrap now backs off 5s/15s/45s/90s on 429 before giving up. --- apps/api/src/collectives/collectives.module.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/api/src/collectives/collectives.module.ts b/apps/api/src/collectives/collectives.module.ts index 748e44c..1f21f3f 100644 --- a/apps/api/src/collectives/collectives.module.ts +++ b/apps/api/src/collectives/collectives.module.ts @@ -26,13 +26,29 @@ class NullProvisioner implements ProvisionerPort { let cachedClient: MatrixHttpClient | null = null; +/** Login is rate-limited by the homeserver per account; a deploy restart can + * trip it (M_LIMIT_EXCEEDED). Retry with backoff instead of failing boot. */ +const LOGIN_RETRY_DELAYS_MS = [5_000, 15_000, 45_000, 90_000]; + /** Lazily logs in the provisioner bot (once per process) and wraps it. */ async function buildProvisioner(logger?: Logger): Promise { const config = loadProvisionerConfig(); if (config === null) return new NullProvisioner(); if (cachedClient === null) { cachedClient = new MatrixHttpClient(config); - await cachedClient.login(); + for (let attempt = 0; ; attempt++) { + try { + await cachedClient.login(); + break; + } catch (error) { + const isLast = attempt >= LOGIN_RETRY_DELAYS_MS.length; + const rateLimited = /M_LIMIT_EXCEEDED|HTTP 429/.test(error instanceof Error ? error.message : String(error)); + if (isLast || !rateLimited) throw error; + const wait = LOGIN_RETRY_DELAYS_MS[attempt] ?? 90_000; + logger?.warn(`provisioner matrix login rate-limited; retry ${attempt + 1} in ${Math.round(wait / 1000)}s`); + await new Promise((resolve) => setTimeout(resolve, wait)); + } + } } return new MatrixProvisioner({ matrix: cachedClient, logger }); }