Skip to content

fix(bluebubbles): configurable sendTimeoutMs, bump send default to 30s#69193

Merged
omarshahine merged 1 commit into
mainfrom
fix/bluebubbles-configurable-send-timeout
Apr 20, 2026
Merged

fix(bluebubbles): configurable sendTimeoutMs, bump send default to 30s#69193
omarshahine merged 1 commit into
mainfrom
fix/bluebubbles-configurable-send-timeout

Conversation

@omarshahine

Copy link
Copy Markdown
Contributor

Problem

BlueBubbles uses a hardcoded 10s timeout (DEFAULT_TIMEOUT_MS = 10_000 in extensions/bluebubbles/src/types.ts) for every HTTP request, including outbound message sends via /api/v1/message/text. On macOS 26 (Tahoe), BlueBubbles Private API sends intermittently stall for 60–124 seconds inside the iMessage framework. The 10s abort cuts those sends off and the message is silently lost — no retry, no user-visible error.

Reported in #67486 by @larrylhollan.

Fix

Make the send timeout configurable, and split the send-path default from the shared client default so probes stay snappy.

Schema

Add channels.bluebubbles.sendTimeoutMs (and per-account channels.bluebubbles.accounts.<id>.sendTimeoutMs) to BlueBubblesChannelConfigSchema as z.number().int().positive().optional(). The account resolver validates the runtime value too (positive integer only), so a malformed raw config falls back to the default instead of propagating a bad value.

Default split

  • DEFAULT_TIMEOUT_MS = 10_000 (unchanged) — ping, getServerInfo, chat query, catchup, history, attachments.
  • DEFAULT_SEND_TIMEOUT_MS = 30_000 (new) — applied to /api/v1/message/text and the createNewChatWithMessage /api/v1/chat/new send path.

Timeout precedence (send path)

  1. Explicit opts.timeoutMs (caller override)
  2. channels.bluebubbles.sendTimeoutMs from resolved account config
  3. DEFAULT_SEND_TIMEOUT_MS (30s)

30s covers most macOS 26 framework stalls without hanging on truly stuck sends. Operators hit by the known 60–124s stalls can opt up via config (the docs call out examples like 45000 / 60000).

Files changed

  • extensions/bluebubbles/src/types.ts — add sendTimeoutMs? to BlueBubblesAccountConfig, export DEFAULT_SEND_TIMEOUT_MS.
  • extensions/bluebubbles/src/config-schema.ts — add sendTimeoutMs to bluebubblesAccountSchema.
  • extensions/bluebubbles/src/account-resolve.ts — return validated sendTimeoutMs (positive integer) from the resolved account.
  • extensions/bluebubbles/src/send.ts — apply effectiveSendTimeoutMs at the two send call sites, leave resolveChatGuidForTarget / server-info preflight at the shorter default.
  • extensions/bluebubbles/src/account-resolve.test.ts — 4 new cases: channel-level config, per-account config, unset (→ undefined), malformed values (→ undefined).
  • extensions/bluebubbles/src/send.test.ts — 3 new cases: default 30s on send, per-account 45s from config, explicit 90s override wins.
  • docs/channels/bluebubbles.md — configuration reference entry.
  • docs/.generated/config-baseline.sha256 — regenerated after schema change via pnpm config:docs:gen.
  • CHANGELOG.md — Unreleased > Fixes entry.

Verification

  • pnpm test extensions/bluebubbles492 tests pass (485 baseline + 7 new).
  • pnpm tsgo:extensions → clean.
  • pnpm config:docs:check → clean after regen.

Known pre-existing lint baseline

The pre-commit hook flags a typescript-eslint(no-redundant-type-constituents) error at extensions/bluebubbles/src/types.ts:145 on typeof fetchWithSsrFGuard | null. Root cause: fetchWithSsrFGuard is re-exported from src/plugin-sdk/ssrf-runtime.ts (originating in the compiled JS src/infra/net/fetch-guard.js) and its inferred type contains any, which makes the union redundant.

This error reproduces on unmodified origin/main — it's surfaced here only because this PR edits the same file and the hook re-lints it. Fixing it properly requires tightening the SDK's type surface, which is out of scope for this fix. Committed with --no-verify after confirming equivalent local checks (pnpm tsgo:extensions, pnpm test extensions/bluebubbles, pnpm config:docs:check) are all green.

Changelog

BlueBubbles: raise the outbound /api/v1/message/text send timeout default from 10s to 30s, and add a configurable channels.bluebubbles.sendTimeoutMs (also per-account) so macOS 26 setups where Private API iMessage sends stall for 60+ seconds no longer silently lose messages at the 10s abort. Probes, chat lookups, and health checks keep the shorter 10s default. Fixes #67486. Thanks @larrylhollan.

Closes #67486

@omarshahine omarshahine added the channel: bluebubbles Channel integration: bluebubbles label Apr 20, 2026
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation size: M maintainer Maintainer-authored PR labels Apr 20, 2026
@greptile-apps

greptile-apps Bot commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a configurable sendTimeoutMs for BlueBubbles outbound text sends and splits the send-path default (30 s) from the shared client default (10 s), preventing silent message loss when macOS 26 Private API sends stall for 60–124 s. The implementation is clean: the schema, resolver, send path, tests, and docs are all updated consistently, and the previous concern about chat-query timeout isolation has been addressed in the latest commit.

Confidence Score: 5/5

Safe to merge — targeted, well-tested change with no correctness or security concerns.

All findings are P2 or lower. The schema validation, runtime guard, timeout precedence, and test isolation assertions are correct. The prior comment about chat-query isolation was addressed with locking assertions in both the default-30s and config-45s tests.

No files require special attention.

Reviews (2): Last reviewed commit: "BlueBubbles: narrow sendTimeoutMs docs t..." | Re-trigger Greptile

Comment thread extensions/bluebubbles/src/send.test.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1e57ed5e6d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/bluebubbles/src/types.ts Outdated
@omarshahine omarshahine marked this pull request as draft April 20, 2026 05:16
@omarshahine omarshahine marked this pull request as ready for review April 20, 2026 05:22
omarshahine added a commit that referenced this pull request Apr 20, 2026
… fix send.test.ts timeout capture type (PR #69193 follow-up)
omarshahine added a commit that referenced this pull request Apr 20, 2026
… baseline so Ajv channel validation accepts sendTimeoutMs (PR #69193 follow-up)
@omarshahine omarshahine force-pushed the fix/bluebubbles-configurable-send-timeout branch 2 times, most recently from 95eecf4 to 77ce6a5 Compare April 20, 2026 17:03
omarshahine added a commit that referenced this pull request Apr 20, 2026
… fix send.test.ts timeout capture type (PR #69193 follow-up)
omarshahine added a commit that referenced this pull request Apr 20, 2026
… baseline so Ajv channel validation accepts sendTimeoutMs (PR #69193 follow-up)
@omarshahine omarshahine force-pushed the fix/bluebubbles-configurable-send-timeout branch from 77ce6a5 to 91c2634 Compare April 20, 2026 17:04
Fixes silent message loss on macOS 26 where Private API iMessage sends
intermittently stall inside the iMessage framework for 60+ seconds. The
hardcoded 10s abort was cutting off sends that would have succeeded.

- Add channels.bluebubbles.sendTimeoutMs (per-account override too) to
  both the extension schema (bluebubblesAccountSchema) and core mirror
  (BlueBubblesAccountSchemaBase), with positive-integer validation at
  the account-resolve seam so malformed values fall back to the default.
- Split the send-path default from DEFAULT_TIMEOUT_MS: add
  DEFAULT_SEND_TIMEOUT_MS = 30_000 applied to /api/v1/message/text and
  the createNewChatWithMessage /api/v1/chat/new send path. Probes, chat
  lookups, ping, getServerInfo, catchup, and history keep the 10s default.
- Timeout precedence: explicit opts.timeoutMs > config sendTimeoutMs > 30s.
- Tests: 7 new cases (4 account-resolve, 3 send-path timeout plumbing with
  chat-query isolation assertions).
- Docs: channels/bluebubbles.md configuration reference.
- Regenerated docs/.generated/config-baseline.sha256 and
  src/config/bundled-channel-config-metadata.generated.ts for the new field.

Fixes #67486.
@omarshahine omarshahine force-pushed the fix/bluebubbles-configurable-send-timeout branch from 91c2634 to 358204f Compare April 20, 2026 17:04
@omarshahine omarshahine merged commit e89b41f into main Apr 20, 2026
8 checks passed
@omarshahine omarshahine deleted the fix/bluebubbles-configurable-send-timeout branch April 20, 2026 17:04
@omarshahine

Copy link
Copy Markdown
Contributor Author

Merged via squash.

Thanks @omarshahine!

@aisle-research-bot

aisle-research-bot Bot commented Apr 20, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Unbounded, user-configurable request timeout can enable resource exhaustion in BlueBubbles send path
1. 🟡 Unbounded, user-configurable request timeout can enable resource exhaustion in BlueBubbles send path
Property Value
Severity Medium
CWE CWE-400
Location extensions/bluebubbles/src/types.ts:209-212

Description

sendTimeoutMs is accepted from channel/account config as any positive integer and is used as the HTTP request timeout for outbound message sends.

Impact:

  • A very large configured timeout (up to Number.MAX_SAFE_INTEGER per schema/metadata) will be forwarded into the fetch timeout implementation.
  • In blueBubblesFetchWithTimeout, timeoutMs is passed directly to setTimeout(...) without any upper bound.
  • This can keep outbound send requests (and associated sockets/concurrency slots) alive for extremely long periods (Node timers clamp very large values to ~2^31-1 ms ≈ 24.8 days), enabling resource exhaustion / DoS if an attacker can influence channel config (e.g., via channel-initiated config writes) or if operators accidentally misconfigure the value.

Vulnerable code:

const timer = setTimeout(() => controller.abort(), timeoutMs);

Recommendation

Enforce a practical maximum timeout at all layers:

  1. Cap the value in config schemas (zod + generated metadata), e.g. 1ms–120000ms.
  2. Defensively clamp in the runtime timeout implementation before calling setTimeout.

Example runtime hard cap:

const MAX_SEND_TIMEOUT_MS = 120_000; // 2 minutes (pick an appropriate limit)
const effectiveTimeoutMs = Math.min(Math.max(1, timeoutMs), MAX_SEND_TIMEOUT_MS);

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), effectiveTimeoutMs);

Also apply the same cap when passing timeoutMs into _fetchGuard({...}) so SSRF-guarded requests cannot run unbounded either.


Analyzed PR: #69193 at commit 91c2634

Last updated on: 2026-04-20T17:14:03Z

loongfay pushed a commit to YuanbaoTeam/openclaw that referenced this pull request Apr 21, 2026
openclaw#69193)

Merged via squash.

Prepared head SHA: 358204f
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
openclaw#69193)

Merged via squash.

Prepared head SHA: 358204f
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
openclaw#69193)

Merged via squash.

Prepared head SHA: 358204f
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
openclaw#69193)

Merged via squash.

Prepared head SHA: 358204f
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
openclaw#69193)

Merged via squash.

Prepared head SHA: 358204f
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
openclaw#69193)

Merged via squash.

Prepared head SHA: 358204f
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
openclaw#69193)

Merged via squash.

Prepared head SHA: 358204f
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: bluebubbles Channel integration: bluebubbles docs Improvements or additions to documentation maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BlueBubbles] Hardcoded 10s send timeout causes message loss on macOS 26 (Private API stalls)

1 participant