fix: guard against uid:"" corruption in remote search resolve; add 404 logging
Deploy Indiekit Server / deploy (push) Successful in 1m22s

- patch-ap-mastodon-misc: ap-search-remote-uid-guard — throw before
  addTimelineItem if extractObjectData returns empty uid, preventing
  a corrupted uid:"" document from being returned as a search result
  and causing TypeError on subsequent like attempts

- patch-ap-mastodon-statuses: ap-status-not-found-log — add
  console.warn to findTimelineItemById for both invalid-ObjectId and
  genuine-not-found cases, making 404 root causes diagnosable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sven
2026-04-15 21:48:22 +02:00
parent a0ba049b6e
commit 0a131f699c
2 changed files with 63 additions and 0 deletions
+35
View File
@@ -258,6 +258,41 @@ const SEARCH_REMOTE_NEW = [
` } // [patch] ap-search-url-resolve`,
].join("\n");
// ── patch-ap-search-remote-uid-guard: skip AP objects with no uid ─────────────
// When extractObjectData fails to resolve the AP object's id (e.g. network
// error or malformed response), it returns uid:"". addTimelineItem({uid:""})
// then matches/creates a single corrupted document shared by ALL failed lookups.
// Any client that receives this garbage status will get a 500 when liking it
// (likePost calls new URL("") which throws). Guard: reject rData with empty uid.
const UID_GUARD_MARKER = "// [patch] ap-search-remote-uid-guard";
// Anchor must be the two consecutive remote-marker lines that exist only here.
const UID_GUARD_OLD =
` const _rData = await extractObjectData(_rObj, { documentLoader: _rDl }); ${SEARCH_REMOTE_MARKER}\n` +
` const _rStored = await addTimelineItem(collections, _rData); ${SEARCH_REMOTE_MARKER}`;
const UID_GUARD_NEW =
` const _rData = await extractObjectData(_rObj, { documentLoader: _rDl }); ${SEARCH_REMOTE_MARKER}\n` +
` if (!_rData?.uid) throw new Error("remote AP object has no uid"); ${UID_GUARD_MARKER}\n` +
` const _rStored = await addTimelineItem(collections, _rData); ${SEARCH_REMOTE_MARKER}`;
let uidGuardDone = false;
for (const f of SEARCH_CANDIDATES) {
if (!(await fileExists(f))) continue;
const src = await readFile(f, "utf8");
if (src.includes(UID_GUARD_MARKER)) {
console.log(`[postinstall] ${SCRIPT}: ap-search-remote-uid-guard already applied in ${f}`);
uidGuardDone = true; break;
}
if (!src.includes(UID_GUARD_OLD)) {
console.warn(`[postinstall] ${SCRIPT}: ap-search-remote-uid-guard — anchor not found in ${f}`);
continue;
}
await writeFile(f, src.replace(UID_GUARD_OLD, UID_GUARD_NEW), "utf8");
console.log(`[postinstall] ${SCRIPT}: applied ap-search-remote-uid-guard to ${f}`);
total++; uidGuardDone = true; break;
}
if (!uidGuardDone) console.log(`[postinstall] ${SCRIPT}: ap-search-remote-uid-guard — no target file found`);
let searchRemoteDone = false;
for (const f of SEARCH_CANDIDATES) {
if (!(await fileExists(f))) continue;
+28
View File
@@ -206,6 +206,33 @@ const NEW_CTX_STATE = ` // Serialize all items
}
const serializeOpts = { baseUrl, favouritedIds: ctxFavouritedIds, rebloggedIds: ctxRebloggedIds, bookmarkedIds: ctxBookmarkedIds, pinnedIds: new Set(), replyIdMap, replyAccountIdMap };`;
// ── Patch 6: ap-status-not-found-log ────────────────────────────────────────
// Add warn logging to findTimelineItemById so 404s on /favourite etc. produce
// a log line showing the requested id and whether it was a bad ObjectId or a
// genuine missing document. Helps diagnose "Record not found" recurrences.
const MARKER_NOT_FOUND_LOG = "// [patch] ap-status-not-found-log";
const OLD_FIND_BY_ID = `async function findTimelineItemById(collection, id) {
try {
return await collection.findOne({ _id: new ObjectId(id) });
} catch {
return null;
}
}`;
const NEW_FIND_BY_ID = `async function findTimelineItemById(collection, id) { ${MARKER_NOT_FOUND_LOG}
try { ${MARKER_NOT_FOUND_LOG}
const _oid = new ObjectId(id); ${MARKER_NOT_FOUND_LOG}
const _doc = await collection.findOne({ _id: _oid }); ${MARKER_NOT_FOUND_LOG}
if (!_doc) console.warn(\`[Mastodon API] findTimelineItemById: no item for id=\${id}\`); ${MARKER_NOT_FOUND_LOG}
return _doc; ${MARKER_NOT_FOUND_LOG}
} catch (_fErr) { ${MARKER_NOT_FOUND_LOG}
console.warn(\`[Mastodon API] findTimelineItemById: invalid id=\${id}: \${_fErr.message}\`); ${MARKER_NOT_FOUND_LOG}
return null; ${MARKER_NOT_FOUND_LOG}
} ${MARKER_NOT_FOUND_LOG}
}`;
// ── Runner ───────────────────────────────────────────────────────────────────
const FILES = apPath("lib/mastodon/routes/statuses.js");
@@ -216,6 +243,7 @@ const SINGLE_PATCHES = [
{ name: "delete-fix-C", marker: MARKER_DELETE_FIX, oldSnippet: OLD_AFTER_DELETE, newSnippet: NEW_AFTER_DELETE },
{ name: "status-reply-id-B", marker: MARKER_REPLY_ID, oldSnippet: OLD_REPLY_INSERT, newSnippet: NEW_REPLY_INSERT },
{ name: "interactions-context", marker: MARKER_CTX_STATE, oldSnippet: OLD_CTX_STATE, newSnippet: NEW_CTX_STATE },
{ name: "not-found-log", marker: MARKER_NOT_FOUND_LOG, oldSnippet: OLD_FIND_BY_ID, newSnippet: NEW_FIND_BY_ID },
];
// Patches that need multiple replacements in one pass (applyMultiPatch)