Skip to content

BlueBubbles: reply threading silently degrades when Private API cache expires #43764

Description

@omarshahine

Draft — validating fix before requesting upstream merge

Summary

BlueBubbles reply threading (iMessage selectedMessageGuid) silently degrades to plain sends after the server info cache expires (~10 minutes). The message tool returns {"ok": true} with no indication that threading was dropped.

Root Cause

  1. monitor.ts fetches fetchBlueBubblesServerInfo() once on startup, caching private_api: true with a 10-minute TTL (probe.ts:23)
  2. After expiry, getCachedBlueBubblesPrivateApiStatus() returns null
  3. isBlueBubblesPrivateApiStatusEnabled(null) returns false (requires === true)
  4. send.ts:438-442 — the if (wantsReplyThread && privateApiDecision.canUsePrivateApi) check fails silently
  5. selectedMessageGuid is never added to the BB API payload
  6. Message sends successfully as plain text — no error, no warning to the agent

Evidence

Gateway logs show this warning on every reply attempt after cache expiry:

[bluebubbles] Private API status unknown; sending without reply threading.
Run a status probe to restore private-api features.

But this warning only appears in gateway logs — the agent never sees it. The tool result returns success.

Inconsistency with Reactions

Reactions handle null differently (reactions.ts:153):

if (getCachedBlueBubblesPrivateApiStatus(accountId) === false) {
  throw new Error("...");
}

Reactions only block when Private API is explicitly disabled (false), allowing unknown (null) to proceed. Replies require === true, blocking on unknown.

Proposed Fix

Lazy-refresh the server info cache in send.ts when Private API status is null and a reply/effect is requested:

// In sendMessageBlueBubbles(), after wantsReplyThread/wantsEffect are set:

if (privateApiStatus === null && (wantsReplyThread || wantsEffect)) {
  const serverInfo = await fetchBlueBubblesServerInfo({
    baseUrl,
    password,
    accountId: account.accountId,
    timeoutMs: 5000,
  }).catch(() => null);
  if (serverInfo) {
    privateApiStatus = getCachedBlueBubblesPrivateApiStatus(account.accountId);
  }
}

This adds at most one extra API call (~5s timeout) on the first reply after cache expiry, and the fetched result repopulates the cache for subsequent sends.

Additional Issue: Built-in Skill Parameter Mismatch

The built-in skills/bluebubbles/SKILL.md documents replyTo as the parameter for action: "reply", but actions.ts:259 reads messageId. The generic action: "send" path in message-action-runner.ts correctly reads replyTo. This means:

  • action=send + replyTo → works (generic path)
  • action=reply + replyTosilently fails (BB path reads messageId)
  • action=reply + messageId → works (BB path)

The skill should either document messageId for action=reply, or actions.ts should also read replyTo as a fallback.

Environment

  • OpenClaw v2026.3.7
  • BlueBubbles with Private API enabled
  • macOS host

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions