From 32ea3753782995f38c2758c2618da16ca6de8c9d Mon Sep 17 00:00:00 2001 From: svemagie <869694+svemagie@users.noreply.github.com> Date: Sun, 14 Jun 2026 18:04:31 +0200 Subject: [PATCH] fix: read inbound mention/hashtag tags via getTags(), not object.tag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A hydrated Fedify object has no .tag property — tags are exposed through the async getTags() iterator. The mention-notification check (if object.tag) and the followed-hashtag check were therefore dead code: every inbound mention failed to create a notification, and followed-tag posts never landed in the timeline. Proven by replaying a real captured Bridgy Create payload: object.tag === undefined while getTags() yields the Mention. Switched both sites to for await (... object.getTags()) with constructor.name type checks. --- lib/inbox-handlers.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/inbox-handlers.js b/lib/inbox-handlers.js index 4bb5e8e..ccf66bc 100644 --- a/lib/inbox-handlers.js +++ b/lib/inbox-handlers.js @@ -781,12 +781,12 @@ export async function handleCreate(item, collections, ctx, handle) { } } - // Check for mentions of our actor - if (object.tag) { - const tags = Array.isArray(object.tag) ? object.tag : [object.tag]; - - for (const tag of tags) { - if (tag.type === "Mention" && tag.href?.href === ourActorUrl) { + // Check for mentions of our actor. + // NB: a hydrated Fedify object exposes tags via async getTags(), NOT a `.tag` + // property — `object.tag` is always undefined, so the old check never ran. + { + for await (const tag of object.getTags({ documentLoader: authLoader })) { + if (tag.constructor?.name === "Mention" && tag.href?.href === ourActorUrl) { const actorInfo = await extractActorInfo(actorObj, { documentLoader: authLoader }); const rawMentionHtml = object.content?.toString() || ""; const mentionHtml = sanitizeContent(rawMentionHtml); @@ -860,10 +860,14 @@ export async function handleCreate(item, collections, ctx, handle) { // Not a followed account — check if the post's hashtags match any followed tags // so tagged posts from across the fediverse appear in the timeline try { - const objectTags = Array.isArray(object.tag) ? object.tag : (object.tag ? [object.tag] : []); - const postHashtags = objectTags - .filter((t) => t.type === "Hashtag" && t.name) - .map((t) => t.name.toString().replace(/^#/, "").toLowerCase()); + // Tags come from async getTags() — `object.tag` is always undefined on a + // hydrated Fedify object (same gotcha as the mention check above). + const postHashtags = []; + for await (const t of object.getTags({ documentLoader: authLoader })) { + if (t.constructor?.name === "Hashtag" && t.name) { + postHashtags.push(t.name.toString().replace(/^#/, "").toLowerCase()); + } + } if (postHashtags.length > 0) { const followedTags = await getFollowedTags(collections);