fix(bluebubbles): add GUID-based dedup to prevent duplicate replies - #22116
Open
rswafford1980 wants to merge 1 commit into
Open
fix(bluebubbles): add GUID-based dedup to prevent duplicate replies#22116rswafford1980 wants to merge 1 commit into
rswafford1980 wants to merge 1 commit into
Conversation
BlueBubbles sends multiple webhook events per incoming message (status updates: delivered, read, etc.). The _handle_webhook method spawns handle_message() for every webhook POST, creating a race window where multiple tasks pass the _active_sessions guard before the first one sets it. Fix adds a 30-second GUID dedup cache at the webhook level, so only the first event per message GUID triggers handle_message. Subsequent events within the TTL window return ok immediately.
Collaborator
13 tasks
teknium1
reviewed
Jul 13, 2026
teknium1
left a comment
Contributor
There was a problem hiding this comment.
Thanks for isolating the inbound webhook duplication path.
Problems
gateway/platforms/bluebubbles.py:70deliberately admitsupdated-messageevents. The GUID-only check added by this PR would drop every same-GUID update, not only identical webhook replays; it needs an event/update fingerprint that preserves meaningful lifecycle updates.- The new local cache duplicates
gateway/platforms/helpers.py:27-71, whereMessageDeduplicatoralready provides TTL pruning and a hard size bound. - The diff has no tests, despite an existing webhook suite in
tests/gateway/test_bluebubbles.py. Regression coverage should exercise exact replay suppression and same-GUID lifecycle updates.
Suggested changes
- Reuse
MessageDeduplicatorand key it with a replay fingerprint rather than GUID alone. - Add focused async webhook tests for replay, distinct GUIDs, and same-GUID update/attachment behavior.
This is an automated hermes-sweeper review.
| @@ -129,6 +130,8 @@ def __init__(self, config: PlatformConfig): | |||
| self._private_api_enabled: Optional[bool] = None | |||
Contributor
There was a problem hiding this comment.
Please reuse gateway.platforms.helpers.MessageDeduplicator rather than adding a second local TTL map. The shared helper already enforces a maximum cache size in addition to TTL pruning.
| self._seen_message_guids = { | ||
| k: v for k, v in self._seen_message_guids.items() | ||
| if now - v < self._DEDUP_TTL | ||
| } |
Contributor
There was a problem hiding this comment.
This suppresses every same-GUID event, but the adapter explicitly accepts updated-message events. Use a replay fingerprint that distinguishes exact retries from meaningful same-GUID lifecycle updates such as edits or attachment completion.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
BlueBubbles sends multiple webhook events per incoming message (5-10 POSTs in rapid succession as iMessage status updates fire: delivered, read, etc.). The
_handle_webhookmethod inbluebubbles.pyspawnsasyncio.create_task(self.handle_message(event))for every webhook hit.Although
handle_message()inbase.pyhas an_active_sessionsguard, the tasks are spawned synchronously but run asynchronously, creating a race window where multiple tasks pass the guard before the first one sets it. Net result: every iMessage gets 2+ duplicate replies from the agent.Fix
Three changes in
gateway/platforms/bluebubbles.py:import timeto imports_seen_message_guidsdict and_DEDUP_TTL = 30.0to__init___handle_webhook— before thehandle_messagespawn, checks if the message GUID was seen within the last 30 seconds. If so, returnsokimmediately. Periodically prunes expired entries.The dedup runs after the existing
isFromMeand tapback filters, so those are unaffected.Test Plan
isFromMeand tapback messages still silently dropped