Skip to content

fix(bluebubbles): dedup webhook replays and skip spam-filtered chats - #86112

Open
noelly wants to merge 1 commit into
NousResearch:mainfrom
noelly:NL-bluebubbles-dedup-filtered-chats-20260814
Open

fix(bluebubbles): dedup webhook replays and skip spam-filtered chats#86112
noelly wants to merge 1 commit into
NousResearch:mainfrom
noelly:NL-bluebubbles-dedup-filtered-chats-20260814

Conversation

@noelly

@noelly noelly commented Aug 14, 2026

Copy link
Copy Markdown

Summary

Two fixes to the BlueBubbles adapter in gateway/platforms/bluebubbles.py:

  • Inbound message dedup — BlueBubbles webhooks can replay after a reconnect, which results in duplicate inbound message processing (double replies, double tool runs). This adds an in-memory seen-id cache (48h window, capped at 4k ids, matching Photon's dedup parameters).
  • Skip spam-filtered chats during GUID resolution — iMessage stores auto-spam-reported threads with masked handles (e.g. any;-;+156****2244 with literal asterisks). Sending to those GUIDs fails with HTTP 500 from the BlueBubbles server, and the real DM for the same number is a separate, non-filtered chat. This skips isFiltered chats when resolving chatIdentifier → chatGuid.

User-visible behavior

  • No more duplicate inbound messages after a BlueBubbles server reconnect.
  • Sending to a phone number whose chat got auto-spam-reported works again (previously the adapter would resolve to the masked/filtered GUID and the send would 500).

Test Plan

  • Reconnect/restart BlueBubbles server while inbound messages are flowing — each message should be processed exactly once.
  • Send to a DM that has been auto-spam-reported by iMessage — the adapter should resolve the non-filtered chat GUID and deliver successfully.
  • Regression: normal DM/group sends and inbound handling behave unchanged.

Notes for Reviewers

  • The dedup cache is in-memory only (per-process), consistent with how the Photon adapter handles the same replay problem.
  • The filtered-chat skip was verified against a real instance Aug 5 2026: ROWID 877 (filtered, asterisk-masked handle) vs ROWID 850 (real DM for the same number).
  • No changes to public API or config; existing environment variables unchanged.

Two fixes for the BlueBubbles adapter:

1. Inbound message dedup: BlueBubbles webhooks can replay after a
   reconnect, which caused duplicate inbound message processing. Track
   seen message ids for 48h (capped at 4k), matching Photon's dedup
   parameters.

2. Skip isFiltered chats when resolving chat GUIDs: iMessage stores
   masked handles (e.g. 'any;-;+156****2244' with literal asterisks)
   for auto-spam-reported threads, and sends to those GUIDs fail with
   500 from the BlueBubbles server. The real DM for the same number is
   a separate, non-filtered chat. Verified Aug 5 2026: ROWID 877
   filtered/asterisks vs ROWID 850 real.
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Aug 14, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: this overlaps the BlueBubbles replay-dedup cluster around #30708 (#33337, #38379, #45717), while also repairing filtered-chat GUID resolution.

@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference, author can ignore or act on any point.

fix(bluebubbles): dedup webhook replays and skip spam-filtered chats

  1. gateway/platforms/bluebubbles.py:_is_duplicate records the message id before the message is processed. If the first delivery fails partway through _handle_webhook (exception), a legitimate replay or retry of the same message is then dropped as a duplicate — the failure is never retried. Consider recording the id only after successful processing, or tracking a processing state so a failed attempt does not consume the dedup slot.

  2. The dedup cache is in-memory only, so a gateway restart within the 48h window re-processes any replay. That is a reasonable tradeoff, but worth documenting as a known limitation (the comment says it matches Photon's parameters, which may also be in-memory).

  3. _resolve_chat_guid now skips any chat with isFiltered set. A contact whose only chat is the filtered thread (auto-spam-reported and never un-filtered, so no separate non-filtered DM exists) becomes unreachable — the GUID is skipped and the send path fails. The verified separate-DM assumption covers the reported case, but a warning when only filtered chats exist (instead of a silent skip) would make the failure mode explicit.

  4. Minor: the size-bound prune (list(seen.keys())[:n]) is O(n) on every insert over 4000 entries — fine at this scale, but _guid_cache in the same file already uses an OrderedDict; using the same pattern for _seen_message_ids would keep the oldest-eviction constant-time and consistent with the existing code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants