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
monitor.ts fetches fetchBlueBubblesServerInfo() once on startup, caching private_api: true with a 10-minute TTL (probe.ts:23)
- After expiry,
getCachedBlueBubblesPrivateApiStatus() returns null
isBlueBubblesPrivateApiStatusEnabled(null) returns false (requires === true)
send.ts:438-442 — the if (wantsReplyThread && privateApiDecision.canUsePrivateApi) check fails silently
selectedMessageGuid is never added to the BB API payload
- 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 + replyTo → silently 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
Summary
BlueBubbles reply threading (iMessage
selectedMessageGuid) silently degrades to plain sends after the server info cache expires (~10 minutes). Themessagetool returns{"ok": true}with no indication that threading was dropped.Root Cause
monitor.tsfetchesfetchBlueBubblesServerInfo()once on startup, cachingprivate_api: truewith a 10-minute TTL (probe.ts:23)getCachedBlueBubblesPrivateApiStatus()returnsnullisBlueBubblesPrivateApiStatusEnabled(null)returnsfalse(requires=== true)send.ts:438-442— theif (wantsReplyThread && privateApiDecision.canUsePrivateApi)check fails silentlyselectedMessageGuidis never added to the BB API payloadEvidence
Gateway logs show this warning on every reply attempt after cache expiry:
But this warning only appears in gateway logs — the agent never sees it. The tool result returns success.
Inconsistency with Reactions
Reactions handle
nulldifferently (reactions.ts:153):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.tswhen Private API status isnulland a reply/effect is requested: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.mddocumentsreplyToas the parameter foraction: "reply", butactions.ts:259readsmessageId. The genericaction: "send"path inmessage-action-runner.tscorrectly readsreplyTo. This means:action=send+replyTo→ works (generic path)action=reply+replyTo→ silently fails (BB path readsmessageId)action=reply+messageId→ works (BB path)The skill should either document
messageIdforaction=reply, oractions.tsshould also readreplyToas a fallback.Environment