fix: add 8 s timeout + logging to actor lookup in resolveRemoteAccount
Deploy Indiekit Server / deploy (push) Successful in 1m19s

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 <noreply@anthropic.com>
This commit is contained in:
Sven
2026-04-15 20:54:10 +02:00
parent 41660a96fe
commit a0ff51e289
+40
View File
@@ -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)`);