fix(bluebubbles): configurable sendTimeoutMs, bump send default to 30s#69193
Conversation
Greptile SummaryThis PR introduces a configurable Confidence Score: 5/5Safe 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 |
There was a problem hiding this comment.
💡 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".
… fix send.test.ts timeout capture type (PR #69193 follow-up)
… baseline so Ajv channel validation accepts sendTimeoutMs (PR #69193 follow-up)
95eecf4 to
77ce6a5
Compare
… fix send.test.ts timeout capture type (PR #69193 follow-up)
… baseline so Ajv channel validation accepts sendTimeoutMs (PR #69193 follow-up)
77ce6a5 to
91c2634
Compare
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.
91c2634 to
358204f
Compare
|
Merged via squash.
Thanks @omarshahine! |
🔒 Aisle Security AnalysisWe found 1 potential security issue(s) in this PR:
1. 🟡 Unbounded, user-configurable request timeout can enable resource exhaustion in BlueBubbles send path
Description
Impact:
Vulnerable code: const timer = setTimeout(() => controller.abort(), timeoutMs);RecommendationEnforce a practical maximum timeout at all layers:
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 Analyzed PR: #69193 at commit Last updated on: 2026-04-20T17:14:03Z |
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
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
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
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
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
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
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
Problem
BlueBubbles uses a hardcoded 10s timeout (
DEFAULT_TIMEOUT_MS = 10_000inextensions/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-accountchannels.bluebubbles.accounts.<id>.sendTimeoutMs) toBlueBubblesChannelConfigSchemaasz.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/textand thecreateNewChatWithMessage/api/v1/chat/newsend path.Timeout precedence (send path)
opts.timeoutMs(caller override)channels.bluebubbles.sendTimeoutMsfrom resolved account configDEFAULT_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— addsendTimeoutMs?toBlueBubblesAccountConfig, exportDEFAULT_SEND_TIMEOUT_MS.extensions/bluebubbles/src/config-schema.ts— addsendTimeoutMstobluebubblesAccountSchema.extensions/bluebubbles/src/account-resolve.ts— return validatedsendTimeoutMs(positive integer) from the resolved account.extensions/bluebubbles/src/send.ts— applyeffectiveSendTimeoutMsat the two send call sites, leaveresolveChatGuidForTarget/ 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 viapnpm config:docs:gen.CHANGELOG.md— Unreleased > Fixes entry.Verification
pnpm test extensions/bluebubbles→ 492 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 atextensions/bluebubbles/src/types.ts:145ontypeof fetchWithSsrFGuard | null. Root cause:fetchWithSsrFGuardis re-exported fromsrc/plugin-sdk/ssrf-runtime.ts(originating in the compiled JSsrc/infra/net/fetch-guard.js) and its inferred type containsany, 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-verifyafter confirming equivalent local checks (pnpm tsgo:extensions,pnpm test extensions/bluebubbles,pnpm config:docs:check) are all green.Changelog
Closes #67486