hermes - 💡(How to fix) Fix BlueBubbles: send_typing/stop_typing silently no-op when helper_connected becomes true after gateway startup (cold-boot race) [1 pull requests]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

On macOS, the BlueBubbles adapter's send_typing() and stop_typing() can permanently no-op after a fresh boot — even when the BB Helper dylib is actually injected into Messages.app and the typing endpoint is fully functional. User-visible symptom: typing indicators stop appearing on iMessage threads even though replies still get through.

Error Message

async def send_typing(self, chat_id: str, metadata=None) -> None:

  • if not self._private_api_enabled or not self._helper_connected or not self.client:
  •    return
  • BlueBubbles /server/info can report helper_connected=false during

  • gateway startup even though the Private API typing endpoint already

  • works (or the helper connects moments later). Don't pin typing to

  • that stale startup bit; the endpoint itself is the source of truth.

  • if not self._private_api_enabled or not self.client:
  •    return
    try: guid = await self._resolve_chat_guid(chat_id) if guid: encoded = quote(guid, safe="")
  •        await self.client.post(
  •        res = await self.client.post(
               self._api_url(f"/api/v1/chat/{encoded}/typing"), timeout=5
           )
  •        res.raise_for_status()
  •        logger.debug("[bluebubbles] sent typing indicator to %s", _redact(guid))
    except Exception as exc:
  •    logger.debug("[bluebubbles] send_typing failed: %s", exc)
  •    logger.debug("[bluebubbles] send_typing failed for %s: %s", _redact(chat_id), exc)

Root Cause

The _helper_connected value is captured once at startup from /api/v1/server/info and never re-checked. The typing endpoint itself is the source of truth — if it returns 200, the indicator fires. Relying on the stale startup value loses information.

Fix Action

Fixed

Code Example

if not self._private_api_enabled or not self._helper_connected or not self.client:
       return

---

async def send_typing(self, chat_id: str, metadata=None) -> None:
-    if not self._private_api_enabled or not self._helper_connected or not self.client:
-        return
+    # BlueBubbles /server/info can report helper_connected=false during
+    # gateway startup even though the Private API typing endpoint already
+    # works (or the helper connects moments later). Don't pin typing to
+    # that stale startup bit; the endpoint itself is the source of truth.
+    if not self._private_api_enabled or not self.client:
+        return
     try:
         guid = await self._resolve_chat_guid(chat_id)
         if guid:
             encoded = quote(guid, safe="")
-            await self.client.post(
+            res = await self.client.post(
                 self._api_url(f"/api/v1/chat/{encoded}/typing"), timeout=5
             )
+            res.raise_for_status()
+            logger.debug("[bluebubbles] sent typing indicator to %s", _redact(guid))
     except Exception as exc:
-        logger.debug("[bluebubbles] send_typing failed: %s", exc)
+        logger.debug("[bluebubbles] send_typing failed for %s: %s", _redact(chat_id), exc)
RAW_BUFFERClick to expand / collapse

Summary

On macOS, the BlueBubbles adapter's send_typing() and stop_typing() can permanently no-op after a fresh boot — even when the BB Helper dylib is actually injected into Messages.app and the typing endpoint is fully functional. User-visible symptom: typing indicators stop appearing on iMessage threads even though replies still get through.

Reproduction

  1. Run hermes-gateway on macOS as a launchd user agent, alongside BlueBubbles Server set as a login item (typical "always-on Mac mini" deployment).
  2. Reboot the Mac. Both services auto-start.
  3. The BB Helper dylib injection into Messages.app takes a few hundred ms after BB Server is up. During that window, /api/v1/server/info reports helper_connected: false.
  4. Hermes connects to BB during init, fetches /api/v1/server/info, and caches self._helper_connected = False.
  5. Moments later the Helper finishes connecting — but Hermes never re-checks. The cached value stays False forever.
  6. send_typing() / stop_typing() (gateway/platforms/bluebubbles.py:605 and :618) hit:
    if not self._private_api_enabled or not self._helper_connected or not self.client:
        return
    …and return silently with no log entry.
  7. Typing indicators never fire for the rest of the gateway's lifetime, until a restart that happens to win the race.

Manual verification that BB itself is fine: POST /api/v1/chat/{encoded}/typing returns {"status":200,"message":"Successfully started typing!"} and the indicator does appear on the recipient device.

Root cause

The _helper_connected value is captured once at startup from /api/v1/server/info and never re-checked. The typing endpoint itself is the source of truth — if it returns 200, the indicator fires. Relying on the stale startup value loses information.

Proposed fix

Drop _helper_connected from the gate in send_typing and stop_typing specifically (keep the gate for things that genuinely require helper attestation, like mark_read). Treat the endpoint as authoritative — raise on HTTP error, debug-log failures:

 async def send_typing(self, chat_id: str, metadata=None) -> None:
-    if not self._private_api_enabled or not self._helper_connected or not self.client:
-        return
+    # BlueBubbles /server/info can report helper_connected=false during
+    # gateway startup even though the Private API typing endpoint already
+    # works (or the helper connects moments later). Don't pin typing to
+    # that stale startup bit; the endpoint itself is the source of truth.
+    if not self._private_api_enabled or not self.client:
+        return
     try:
         guid = await self._resolve_chat_guid(chat_id)
         if guid:
             encoded = quote(guid, safe="")
-            await self.client.post(
+            res = await self.client.post(
                 self._api_url(f"/api/v1/chat/{encoded}/typing"), timeout=5
             )
+            res.raise_for_status()
+            logger.debug("[bluebubbles] sent typing indicator to %s", _redact(guid))
     except Exception as exc:
-        logger.debug("[bluebubbles] send_typing failed: %s", exc)
+        logger.debug("[bluebubbles] send_typing failed for %s: %s", _redact(chat_id), exc)

Defensive alternative if you'd prefer to keep the gate: on the first send_typing after _helper_connected=False, re-fetch /server/info and update the cache, so the value converges within one typing attempt.

Happy to submit as a PR.

Environment

  • Hermes Agent v0.15.1 (2026.5.29)
  • macOS 26.5 Tahoe, Apple Silicon (M4)
  • BlueBubbles Server 1.9.9 with Private API + Helper

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - 💡(How to fix) Fix BlueBubbles: send_typing/stop_typing silently no-op when helper_connected becomes true after gateway startup (cold-boot race) [1 pull requests]