fix(bluebubbles): prevent duplicate inbound turns on v0.19.0 - #45317
fix(bluebubbles): prevent duplicate inbound turns on v0.19.0#45317Oceanswave wants to merge 8 commits into
Conversation
tonydwb
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Approved
Clean, well-scoped fix/feature with comprehensive tests. No issues found.
- Logic is correct and focused
- Tests cover the new behavior
- No security concerns
- Good error handling
Reviewed by Hermes Agent
|
Maintainer note: I updated the PR description to match the final behavior after the follow-up commits. The final version uses an immediate BlueBubbles tapback/reaction when processing starts and intentionally does not send a delayed |
53cfde9 to
bc0eb1a
Compare
|
Follow-up after review: I found and fixed two edge-case regressions in the rebased PR. Distinct rapid attachment-only messages now include attachment identifiers in the short-lived content-dedupe key, so photo bursts are preserved while duplicate webhook variants still collapse. Existing/legacy inbound |
|
Follow-up from searching related BlueBubbles issues/PRs: I found several directly related bugs that were not fully represented in the PR body, so I expanded the PR and added code coverage for the ones that fit this consolidation. New required fixes included here:
I also updated the PR body to distinguish direct closes from partial/related follow-ups: #30989, #31534, #8513 are partially addressed; #46624, #33489, and #23409 remain intentionally out of scope. Validation after the new changes: |
|
I pulled this PR locally, merged current Resolution summary:
Verification on the resolved branch: I tried to push directly to the PR head branch, but GitHub denied write access to Commit: You should be able to cherry-pick/merge that commit or use the branch as a conflict-resolution reference. |
|
Status bump (July 10): verified the PR head still defaults |
4a62e9a to
bd6fe37
Compare
|
Rebased onto current upstream |
|
Senior review follow-up: fixed three additional correctness/documentation smells after reviewing the rebased integration end-to-end. (1) GUID-less inbound payloads no longer get a 60-second text key that could swallow two legitimate repeated replies; they use only the short content-dedupe window. (2) Existing BlueBubbles webhook registrations are now reused only when their event set exactly matches config; stale/duplicate registrations are replaced so |
|
Second senior-review pass (independent reviewer findings incorporated): rapid same-text messages with distinct GUIDs/same payload shape are now both dispatched; content fallback only collapses cross-shape BlueBubbles webhook variants and no longer extends a sliding suppression window. |
|
Typing persistence follow-up pushed in d007f4a54. BlueBubbles now refreshes the typing event every 4 seconds while a turn is active, deduplicates refresh tasks per chat, cancels them before stop-typing, and cleans them up on disconnect. Added configurable typing_refresh_interval, docs, and regression coverage. Focused BlueBubbles/config validation: 265 passed; Ruff, py_compile, and diff checks passed. |
d007f4a to
501ee57
Compare
|
Applied the latest BlueBubbles review feedback and rebased onto current Changes in
Fresh validation after the rebase: The PR body now reflects the current behavior and validation counts. |
|
Thanks @thesammyagent — your conflict-resolution notes and branch were useful, especially the I have since rebased #45317 onto current Current verification: GitHub now reports the PR as MERGEABLE; the remaining BLOCKED state is review/workflow policy rather than a conflict. Thanks again for taking the time to reproduce and validate the resolution. |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for consolidating the BlueBubbles hardening and for preserving the existing strict chat-GUID behavior.
Problems
gateway/platforms/bluebubbles.py:1287-1298commits dedupe state before attachment retrieval. An attachment-only webhook whose download fails reaches the missing-fields path at line 1377, but a retry is then dropped for the dedupe TTL. Please commit the key only after the event is viable, or remove it on that failure path.- The new adapter-local typing task at
gateway/platforms/bluebubbles.py:1008-1012overlaps the shared typing loop.gateway/platforms/base.py:4851-4869starts_keep_typing()for each inbound event, and that loop invokessend_typing()every two seconds (gateway/platforms/base.py:3837-3859), sotyping_refresh_intervalcannot control the normal inbound cadence.
Suggested changes
- Add a retry regression for a transient attachment-download failure.
- Make the base lifecycle the sole typing scheduler, with a platform-specific interval if needed, and test through the background processing path.
Automated hermes-sweeper review.
501ee57 to
927d5ed
Compare
927d5ed to
91ed7dd
Compare
|
Rebased onto current Key follow-up fixes:
Validation: 216 focused/adjacent gateway tests passed; Ruff, |
91ed7dd to
05e86f8
Compare
|
Requesting changes because the attachment retry contract does not match BlueBubbles’ actual behavior. BlueBubbles performs one fire-and-forget Axios POST and only logs non-2xx failures; it does not retry the webhook. Returning 503 whenever any attachment download fails therefore permanently drops captioned messages and successful siblings in multi-attachment messages that current main still delivers. The new test passes only by manually invoking Please use bounded internal attachment retries, then preserve valid caption text and successfully downloaded attachments after exhaustion rather than relying on redelivery. Please also make registration migration cancellation-safe/ownership-aware and add provider-realistic single-delivery coverage. The GUID/in-flight dedup itself looks sound. |
05e86f8 to
6253e4d
Compare
|
Addressed in Attachment delivery now matches the provider contract:
Registration migration is now cancellation-safe and ownership-aware:
Fresh validation on current |
9e3ff08 to
3c9805a
Compare
3c9805a to
d932295
Compare
|
Rebased onto current main and narrowed the branch in d9322959f to the intended single-delivery inbound fix. The unrelated webhook registration ownership/migration rewrite and associated docs/tests were removed. Retained behavior covers GUID dedupe, updated-message suppression, bounded attachment retries, recoverable sibling/caption preservation, attachment-unavailable fallback, and failed/cancelled handoff release. All 26 BlueBubbles adapter tests pass; Ruff, py_compile, Windows footgun scan, ancestry, and diff checks pass. |
d932295 to
4e0cf34
Compare

Problem
BlueBubbles can deliver one incoming iMessage as both
new-messageandupdated-message, sometimes with different chat-ID shapes. Hermes could therefore start two agent turns and send duplicate replies.The prior review-driven branch reduction fixed that inbound problem, but it also removed the existing BlueBubbles reply experience while leaving its configuration in place. Reactions stopped, the configured typing cadence was not used, paragraph replies were split into several iMessage bubbles, and YAML behavior values could be overwritten by defaults.
Related: #30708, #34372, #34378.
What changed
Provider-realistic inbound delivery
new-messageand the legacymessagealias as user-message events.updated-messageevents without starting an agent turn.Reply experience
likereaction when processing starts. No text acknowledgement is sent. Reaction aliases are normalized to BlueBubbles' canonical set, invalid values fall back tolike, and unsuccessful response envelopes are not reported as success.BasePlatformAdapteras the only typing scheduler. BlueBubbles supplies its configured refresh interval and one-shot start/stop API calls."false".config.yaml; only explicit environment values override supported environment-backed settings.Reviewer guidance preserved
new-messageevent set. Stale registrations are migrated with shielded cancellation and rollback/reconciliation after ambiguous failures.Validation on current
maingit diff --checkand conflict-marker scan: passed.