From a0ff51e289c38a5b9716a9da40ddb3fe4f7e7b3b Mon Sep 17 00:00:00 2001 From: Sven Date: Wed, 15 Apr 2026 20:54:10 +0200 Subject: [PATCH] fix: add 8 s timeout + logging to actor lookup in resolveRemoteAccount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lookupWithSecurity → ctx.lookupObject had no timeout, so clicking a @mention handle for an account not in local collections could hang indefinitely or fail silently with no log entry. Now caps the lookup at 8 s and logs "Actor lookup failed" / "returned null" so server logs will show exactly why @mention lookups fail. Co-Authored-By: Claude Sonnet 4.6 --- scripts/patch-ap-mastodon-misc.mjs | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/scripts/patch-ap-mastodon-misc.mjs b/scripts/patch-ap-mastodon-misc.mjs index 3d87590a..d1eb40c8 100644 --- a/scripts/patch-ap-mastodon-misc.mjs +++ b/scripts/patch-ap-mastodon-misc.mjs @@ -219,4 +219,44 @@ for (const f of SEARCH_CANDIDATES) { } if (!searchDone) console.log(`[postinstall] ${SCRIPT}: ap-search-url-resolve — no target file found or no changes`); +// ── patch-ap-resolve-actor-timeout: cap actor lookup at 8 s + log failures ─── +// When Mastodon client clicks a @mention handle, /api/v1/accounts/lookup calls +// resolveRemoteAccount → lookupWithSecurity → ctx.lookupObject (no timeout). +// If the remote server hangs, the request hangs indefinitely. Even when it fails +// fast, we get 404 with no log entry explaining why. +// Fix: wrap lookupWithSecurity in an 8 s timeout; log on failure/null result. + +const ACTOR_TIMEOUT_MARKER = "// [patch] ap-resolve-actor-timeout"; +const RESOLVE_ACCOUNT_CANDIDATES = apPath("lib/mastodon/helpers/resolve-account.js"); + +const ACTOR_TIMEOUT_OLD = ` // Use signed→unsigned fallback so servers rejecting signed GETs still resolve + const documentLoader = await ctx.getDocumentLoader({ identifier: handle }); + const actor = await lookupWithSecurity(ctx, actorUri, { documentLoader }); + if (!actor) return null;`; + +const ACTOR_TIMEOUT_NEW = ` // Use signed→unsigned fallback so servers rejecting signed GETs still resolve + const documentLoader = await ctx.getDocumentLoader({ identifier: handle }); + // Timeout guard: cap actor fetch at 8 s so hung lookups fail fast. ${ACTOR_TIMEOUT_MARKER} + const _aLookupTimeout = (p, ms = 8000) => { const t = new Promise((_, rej) => setTimeout(() => rej(new Error("actor lookup timeout")), ms)); p.catch(() => {}); return Promise.race([p, t]); }; ${ACTOR_TIMEOUT_MARKER} + const actor = await _aLookupTimeout(lookupWithSecurity(ctx, actorUri, { documentLoader })).catch(err => { console.warn(\`[Mastodon API] Actor lookup failed for \${acct}: \${err.message}\`); return null; }); ${ACTOR_TIMEOUT_MARKER} + if (!actor) { console.warn(\`[Mastodon API] lookupWithSecurity returned null for \${acct}\`); return null; } ${ACTOR_TIMEOUT_MARKER}`; + +let actorTimeoutDone = false; +for (const f of RESOLVE_ACCOUNT_CANDIDATES) { + if (!(await fileExists(f))) continue; + const src = await readFile(f, "utf8"); + if (src.includes(ACTOR_TIMEOUT_MARKER)) { + console.log(`[postinstall] ${SCRIPT}: ap-resolve-actor-timeout already applied in ${f}`); + actorTimeoutDone = true; break; + } + if (!src.includes(ACTOR_TIMEOUT_OLD)) { + console.warn(`[postinstall] ${SCRIPT}: ap-resolve-actor-timeout — anchor not found in ${f}`); + continue; + } + await writeFile(f, src.replace(ACTOR_TIMEOUT_OLD, ACTOR_TIMEOUT_NEW), "utf8"); + console.log(`[postinstall] ${SCRIPT}: applied ap-resolve-actor-timeout to ${f}`); + total++; actorTimeoutDone = true; break; +} +if (!actorTimeoutDone) console.log(`[postinstall] ${SCRIPT}: ap-resolve-actor-timeout — no target file found or no changes`); + console.log(`[postinstall] ${SCRIPT}: done (${total} patch(es) applied)`);