fix: guard against uid:"" corruption in remote search resolve; add 404 logging
Deploy Indiekit Server / deploy (push) Successful in 1m22s
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:
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user