← Back to PRs

#23483: fix(bluebubbles): key debounce by chat+sender instead of messageId

by saucesteals open 2026-02-22 10:43 View on GitHub →
channel: bluebubbles size: XS
## Summary - **Root cause:** `buildKey` in the BlueBubbles debouncer https://github.com/openclaw/openclaw/blob/b13fc7eccdc74324c269927e83147abfdda12149/extensions/bluebubbles/src/monitor.ts#L155-L179 makes a wrong assumption: that a URL balloon's `associatedMessageGuid` links it back to the originating text. In practice, iMessage emits URL previews as fully independent messages — `associated_message_guid` is always empty for balloons (confirmed via `chat.db`). Every message therefore gets its own isolated debounce buffer, making `debounceMs` a no-op that only adds latency. - **The fix:** Key by `chatGuid:senderId` instead, matching how Discord https://github.com/openclaw/openclaw/blob/b13fc7eccdc74324c269927e83147abfdda12149/src/discord/monitor/message-handler.ts#L32-L45 and the native iMessage integration https://github.com/openclaw/openclaw/blob/b13fc7eccdc74324c269927e83147abfdda12149/src/imessage/monitor/monitor-provider.ts#L186-L195 already work. All rapid-fire events from the same sender in the same chat share one buffer and coalesce correctly. ## Change Type (select all) - [x] Bug fix ## Scope (select all touched areas) - [x] Integrations ## Linked Issue/PR - Closes # - Related # ## User-visible / Behavior Changes Sending a URL in iMessage previously caused the agent to respond twice — once for the text, once for the URL balloon. After this fix, both events coalesce into a single turn within the debounce window. The `messages.inbound.debounceMs` / `messages.inbound.byChannel.bluebubbles` config now has meaningful effect for BlueBubbles. ## Security Impact (required) - New permissions/capabilities? No - Secrets/tokens handling changed? No - New/changed network calls? No - Command/tool execution surface changed? No - Data access scope changed? No ## Repro + Verification ### Environment - OS: macOS 15.5 - Runtime/container: Node v25.6.1 - Integration/channel: BlueBubbles (iMessage) ### Steps 1. Send a message containing a URL from an iMessage contact (e.g. `check this out https://example.com`) 2. Observe two `new-message` webhook events arrive — one for the text, one for the URL balloon — with unrelated GUIDs ### Expected - One agent response with both the text and URL available ### Actual (before fix) - Two separate agent responses — text and balloon flush from independent debounce buffers ## Evidence SQLite query on `~/Library/Messages/chat.db` confirming iMessage protocol behavior: ```sql SELECT guid, text, balloon_bundle_id, associated_message_guid, associated_message_type FROM message WHERE balloon_bundle_id IS NOT NULL ORDER BY date DESC LIMIT 5; ``` ``` 6652FF55-...|https://kernel.sh|com.apple.messages.URLBalloonProvider||0 71F9E491-...|https://kernel.sh|com.apple.messages.URLBalloonProvider||0 ``` `associated_message_guid` is always empty for URL balloons — Apple provides no protocol-level link between the text message and its preview. ## Human Verification (required) - Verified scenarios: URL send from iMessage → single agent response - Edge cases checked: Plain text (no URL), tapbacks, group chat messages — all unaffected - What you did **not** verify: MMS attachments, sticker balloons, Digital Touch ## Compatibility / Migration - Backward compatible? Yes - Config/env changes? No — existing `messages.inbound.debounceMs` config now works correctly for BlueBubbles - Migration needed? No ## Failure Recovery (if this breaks) - How to disable/revert: Revert this commit and restart the gateway - Files/config to restore: `extensions/bluebubbles/src/monitor.ts` - Known bad symptoms: Two rapid messages from the same sender merge into one turn → lower `messages.inbound.debounceMs` or revert ## Risks and Mitigations - **Risk:** Two genuinely separate rapid messages from the same sender could coalesce into one turn - **Mitigation:** Same behavior as Discord and native iMessage. The window is configurable via `messages.inbound.debounceMs`; default 500ms is negligible in normal conversation. <!-- greptile_comment --> <h3>Greptile Summary</h3> Fixes BlueBubbles debouncing by keying on `chatGuid:senderId` instead of `messageId` or `associatedMessageGuid`. The old approach failed because iMessage URL preview balloons arrive as independent messages with empty `associated_message_guid` fields, causing each webhook event to have its own debounce buffer and preventing coalescence. **Key changes:** - Removed 17 lines of `buildKey` logic that attempted to use `balloonBundleId` + `associatedMessageGuid` or fallback to `messageId` - Now uses `chatGuid:senderId` pattern matching Discord (`discord:${accountId}:${channelId}:${authorId}`) and native iMessage (`imessage:${accountId}:${conversationId}:${sender}`) - This allows rapid messages from the same sender in the same chat to coalesce into a single turn within the debounce window **Impact:** - URL messages that previously triggered two agent responses (one for text, one for URL balloon) now correctly coalesce into one - The `messages.inbound.debounceMs` config now functions as intended for BlueBubbles <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk - The fix is well-researched with SQLite evidence, aligns with existing Discord and iMessage patterns, and simplifies the codebase by removing incorrect assumptions. The test suite validates the coalescence behavior still works correctly with the new keying strategy. The only minor issue is an outdated test description. - No files require special attention <sub>Last reviewed commit: bb820da</sub> <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> <!-- /greptile_comment -->

Most Similar PRs