Skip to content

fix: prevent BlueBubbles duplicate replies - #31639

Open
ben-z wants to merge 3 commits into
NousResearch:mainfrom
ben-z:fix/bluebubbles-rest-send
Open

ben-z wants to merge 3 commits into
NousResearch:mainfrom
ben-z:fix/bluebubbles-rest-send

Conversation

@ben-z

@ben-z ben-z commented May 24, 2026

Copy link
Copy Markdown

Summary

  • send BlueBubbles outbound fallback messages via REST-only helper without starting the webhook listener
  • dedupe BlueBubbles updated-message metadata events before session-key derivation
  • preserve real edit handling by routing changed-text updates as explicit edit/correction turns
  • stop the typing loop before final response delivery so iMessage does not show typing after the reply arrives
  • keep iMessage typing active during processing and log typing start/stop failures instead of swallowing them

Test Plan

  • /Users/ben/.hermes/hermes-agent/venv/bin/python -m pytest tests/gateway/test_bluebubbles.py tests/gateway/test_ephemeral_reply.py tests/gateway/test_keep_typing_timeout.py -q -o 'addopts='
  • /Users/ben/.hermes/hermes-agent/venv/bin/python -m py_compile gateway/platforms/base.py gateway/platforms/bluebubbles.py tests/gateway/test_bluebubbles.py
  • git diff --check -- gateway/platforms/base.py gateway/platforms/bluebubbles.py tests/gateway/test_bluebubbles.py

Use a REST-only outbound path for send_message/cron BlueBubbles sends so they do not start the inbound webhook listener and collide with a running gateway.

Also redact BlueBubbles password query parameters in surfaced errors and isolate webhook environment variables in BlueBubbles tests.
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery comp/tools Tool registry, model_tools, toolsets labels May 24, 2026
@alt-glitch

Copy link
Copy Markdown
Contributor

Duplicate of #12439 which addresses the same BlueBubbles webhook port bind collision in the standalone send_message path. Also related to #27542 (another duplicate of #12439).

@ben-z ben-z changed the title fix: send BlueBubbles messages without webhook listener fix: prevent BlueBubbles duplicate replies May 24, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing a real BlueBubbles standalone-send failure. Current main still calls adapter.connect() from tools/send_message_tool.py:1806, and that method starts the webhook TCPSite at gateway/platforms/bluebubbles.py:281-295, so the port-collision premise remains valid.

Problems

  • tools/send_message_tool.py:1612-1619 reproduces the adapter's HTTP-client setup and ping/server-info handshake already owned by BlueBubblesAdapter.connect() at gateway/platforms/bluebubbles.py:239-269. Keeping both lifecycle implementations will drift.
  • The added test validates that the copied tool path skips connect(), but it does not define or test the adapter-level outbound-only lifecycle. Related salvage PR #51763 supplies connect(send_only=True) plus tests that default gateway startup still binds/registers and send-only teardown leaves gateway-owned state alone.

Suggested changes

  • Keep the regression goal, but make _send_bluebubbles() call an adapter-owned connect(send_only=True) path and preserve connect() as the single handshake implementation.
  • Gate disconnect cleanup on webhook-runner ownership so one-shot sends cannot unregister the gateway webhook or overwrite its runtime status.

This is an automated hermes-sweeper review; humans make the final merge/salvage decision.

client = httpx.AsyncClient(timeout=30.0)
else:
client = httpx.AsyncClient(timeout=30.0, limits=limits)
try:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep this handshake in BlueBubblesAdapter.connect() behind an outbound-only mode rather than duplicating it here. The adapter already owns the client lifecycle, ping/server-info state, webhook registration, and runtime-status behavior; #51763's connect(send_only=True) approach also protects send-only teardown from touching gateway-owned webhook state.

@teknium1 teknium1 added 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 sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 13, 2026
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 comp/tools Tool registry, model_tools, toolsets P3 Low — cosmetic, nice to have sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades 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