Skip to content

feat(bluebubbles): replay missed webhook messages after gateway restart (#66721)#66857

Merged
omarshahine merged 1 commit intomainfrom
lobster/bb-catchup-squash
Apr 15, 2026
Merged

feat(bluebubbles): replay missed webhook messages after gateway restart (#66721)#66857
omarshahine merged 1 commit intomainfrom
lobster/bb-catchup-squash

Conversation

@omarshahine
Copy link
Copy Markdown
Contributor

Summary

Fixes #66721. Adds an in-process startup catchup pass to the BlueBubbles channel that queries BB Server for messages delivered while the gateway was unreachable and replays them through the existing processMessage pipeline.

The hole this closes: BB Server's WebhookService is fire-and-forget on POST failure (no retries) and BB's MessagePoller only re-fires webhooks on BB-side reconnection events (Messages.app / APNs), not on webhook-receiver recovery. Messages delivered while the gateway was down, restarting, or wedged were permanently lost — verified with a controlled experiment on macOS.

This PR supersedes #66853 (which was the stacked follow-up to #66760 / dedupe PR #66816). Same diff, collapsed to a single commit for cleaner review. History of review feedback is preserved in the superseded PR trail; all P1 and P2 findings from Greptile / Codex / Aisle were addressed in-branch before this squash.

Design

  • New extensions/bluebubbles/src/catchup.ts:
    • fetchBlueBubblesMessagesSince(sinceMs, limit, opts) calls /api/v1/message/query with {after, sort:"ASC", with:["chat","chat.participants","attachment"]} so replays carry the same shape normalizeWebhookMessage already handles on live dispatch.
    • loadBlueBubblesCatchupCursor / saveBlueBubblesCatchupCursor persist a single {lastSeenMs, updatedAt} per account under <stateDir>/bluebubbles/catchup/<accountId>__<hash>.json, using the plugin-sdk's atomic JSON helpers. File layout mirrors the inbound-dedupe store from fix(bluebubbles): dedupe inbound webhooks across restarts (#19176, #12053) #66816, and the resolver is the canonical openclaw/plugin-sdk/state-paths.resolveStateDir (same helper dedupe uses) so the two stores share a single root.
    • runBlueBubblesCatchup(target) orchestrates: clamp config, fetch, filter isFromMe and pre-cursor records, dispatch to processMessage, advance cursor.
  • Modified monitor.ts: fire catchup as a background task after webhook target registers; errors are logged but never block the channel-ready signal.
  • Modified config-schema.ts: new optional catchup block (enabled, maxAgeMinutes, perRunLimit, firstRunLookbackMinutes); defaults on with 2h lookback / 50 msg cap / 30-min first-run lookback.
  • Modified accounts.ts: adds catchup to the account-merge nestedObjectKeys list so per-account overrides deep-merge on top of channel-level defaults, mirroring the existing network precedent.

Why this approach

The fix mirrors a workspace-level shell script that's been running on a real OpenClaw install for ~4 weeks (~100 LoC of bash + python doing the same query/filter/POST flow). Porting it into the BB channel itself means every install gets recovery for free, calls processMessage directly (no re-POST hop), and benefits from #66816's persistent dedupe automatically.

Safety

  • Goes through the same processMessage path webhooks use, so auth, allowlist, pairing, and downstream agent dispatch all apply unchanged.
  • Dedupes against fix(bluebubbles): dedupe inbound webhooks across restarts (#19176, #12053) #66816's persistent inbound GUID cache: a webhook delivery that already succeeded cannot be reprocessed by catchup.
  • Never dispatches isFromMe records (double-checked before and after normalization) so the agent's own sends cannot enter the inbound path.
  • Catchup runs once per gateway startup and does NOT skip on rapid restarts — skipping would permanently lose any messages that arrived during the brief downtime between the two startups.
  • Cursor only advances to nowMs on fully-successful runs. On processMessage failure, the cursor is held just before the earliest failure timestamp so the next run retries from there. On truncation (fetchedCount === perRunLimit), the cursor advances only to the last-fetched timestamp so the next gateway startup picks up the unfetched tail.
  • A future-dated cursor (NTP rollback, manual clock adjust) is treated as unusable and falls through to the firstRunLookback path; the cursor is repaired at the end of the run.
  • First-run lookback clamped to the maxAge ceiling so maxAgeMinutes: 5, firstRunLookbackMinutes: 30 cannot exceed the operator's stated cap.
  • Hard ceilings: 12h max lookback, 500 messages per run.
  • Loud WARNING emitted when fetchedCount hits perRunLimit so operators know a single startup didn't drain the full backlog.

Validation

Automated

  • New scoped tests in extensions/bluebubbles/src/catchup.test.ts (21 cases): cursor round-trip, per-account scoping, filesystem-unsafe account IDs, firstRunLookback default and maxAge clamp, enabled: false, rapid-restart-still-runs, isFromMe filter (pre- and post-normalization), query-failure-preserves-cursor, per-message failure isolation, held-cursor-on-retryable-failure, clamp-to-prior-cursor, future-cursor recovery, pre-cursor defense-in-depth, perRunLimit warn / no-warn, and truncation-cursor advances only to page boundary.
  • Full BlueBubbles suite passes: 410/410.
  • pnpm check green (madge, tsgo, oxlint, webhook-auth-body-order, no-pairing-store-group, pairing-account-scope).

Live end-to-end (macOS, BB Server 1.9.x, 2026-04-14)

Repeating the original repro from #66721's issue body with the new in-process catchup:

  1. Stopped gateway cleanly. Verified port refused, no process.
  2. Sent 3 distinct iMessages from a second device. BB-server log shows all 3 dispatches failed with connect ECONNREFUSED 127.0.0.1:18789 and never retried.
  3. Started gateway. Webhook target registered; catchup fired in the background.
  4. Gateway log:
    [bluebubbles] [default] BlueBubbles catchup:
      replayed=3 skipped_fromMe=0 skipped_preCursor=0 failed=0 fetched=3 window_ms=517184
    
  5. All 3 messages produced agent replies delivered back via BB outbound. Persistent cursor file appeared at ~/.openclaw/bluebubbles/catchup/<accountId>__<hash>.json. Subsequent gateway restart with no new inbound activity logged replayed=0 fetched=0 (no-op).

Test plan

  • pnpm test extensions/bluebubbles/src/catchup.test.ts — 21/21
  • pnpm test extensions/bluebubbles/ — 410/410
  • pnpm check — green
  • Live macOS end-to-end repro
  • Maintainer review

History (for reviewer context)

This PR carries ~11 hours of iterative bot review that happened on the prior PRs (#66760#66853). Squashing here for clean review; the findings addressed were:

  • Greptile P2 — align state-dir with canonical SDK resolver; warn on perRunLimit truncation
  • Codex P1 — hold cursor on retryable processMessage failures
  • Codex P1 — always run catchup on startup (no min-interval skip)
  • Codex P1 — keep cursor behind unfetched pages when perRunLimit is hit
  • Codex P2 — clamp first-run window to maxAge
  • Codex P2 — deep-merge catchup overrides at account level
  • Codex P2 — treat future-dated cursor as unusable
  • Codex P2 — clock-skew gate precondition (later obviated by removing the gate)
  • Aisle — 2 of 5 findings apply (password-in-URL and OPENCLAW_STATE_DIR symlink); both are cross-cutting BB-plugin patterns best addressed in separate PRs against the SDK/plugin conventions. Other 3 Aisle findings were in files this PR doesn't touch (stale-SHA scan from pre-rebase).

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 14, 2026

🔒 Aisle Security Analysis

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

# Severity Title
1 🟠 High BlueBubbles API password embedded in URL query string (leaks via logs/redirects/telemetry)
2 🟠 High Unbounded response buffering and JSON parsing in BlueBubbles catchup can cause memory/CPU DoS
3 🟡 Medium BlueBubbles password may leak to logs via unsanitized error strings (URL query contains password)
4 🔵 Low Log injection via unsanitized BlueBubbles accountId in catchup/monitor logging
1. 🟠 BlueBubbles API password embedded in URL query string (leaks via logs/redirects/telemetry)
Property Value
Severity High
CWE CWE-598
Location extensions/bluebubbles/src/types.ts:128-139

Description

buildBlueBubblesApiUrl appends the BlueBubbles API password as a password query parameter. This is risky because secrets in URLs commonly leak via:

  • application/access logs, reverse proxies, APM/telemetry, or error traces that record full URLs
  • intermediary caches or monitoring systems
  • redirects (Node fetch follows redirects by default; the redirected request URL still contains the secret)

In this diff, the new catchup path (catchup.ts) calls buildBlueBubblesApiUrl({ ..., password }), expanding the blast radius of this pattern.

Vulnerable code:

if (params.password) {
  url.searchParams.set("password", params.password);
}

Recommendation

Avoid putting secrets in URLs. Prefer an Authorization header (or another header expected by the server) and keep the URL free of credentials.

Example approach:

  1. Keep buildBlueBubblesApiUrl free of secrets:
export function buildBlueBubblesApiUrl(params: { baseUrl: string; path: string }): string {
  const normalized = normalizeBlueBubblesServerUrl(params.baseUrl);
  const url = new URL(params.path, `${normalized}/`);
  return url.toString();
}
  1. Attach the secret at request time via header:
const url = buildBlueBubblesApiUrl({ baseUrl, path: "/api/v1/message/query" });
const res = await blueBubblesFetchWithTimeout(
  url,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${password}`, // or server-required scheme
    },
    body,
    redirect: "manual", // optionally prevent leaking credentials on redirects
  },
  timeoutMs,
  ssrfPolicy,
);

If the upstream BlueBubbles API only supports query-string authentication, implement explicit redaction wherever URLs are logged/returned in errors, and consider setting redirect: "manual"/"error" to avoid forwarding secrets to redirected locations.

2. 🟠 Unbounded response buffering and JSON parsing in BlueBubbles catchup can cause memory/CPU DoS
Property Value
Severity High
CWE CWE-400
Location extensions/bluebubbles/src/types.ts:149-173

Description

blueBubblesFetchWithTimeout() buffers the entire HTTP response body into memory via response.arrayBuffer() (when the SSRF guard path is used) and returns a new Response containing those bytes. The catchup code then calls await res.json() on that buffered response.

This creates an unbounded memory/CPU risk:

  • No maximum response size is enforced before buffering/parsing.
  • A malicious/compromised BlueBubbles server (or misconfigured baseUrl) can return an extremely large JSON payload.
  • The gateway will buffer the whole payload and then parse it as JSON, potentially causing process OOM or event-loop blocking.
  • Catchup is triggered automatically on startup (fire-and-forget), so the DoS can occur without a user action.

Vulnerable flow:

  • Source: remote HTTP response from /api/v1/message/query
  • Buffering sink: response.arrayBuffer()
  • Parsing sink: res.json()

Recommendation

Enforce a maximum response size and avoid unbounded buffering/parsing.

Options (prefer streaming limits):

  1. Add a hard max bytes cap (e.g., 1–5 MB) when reading the body. If Content-Length exceeds the cap, abort early.
  2. Stream and incrementally enforce limits while reading response.body.
  3. In fetchBlueBubblesMessagesSince, request minimal expansions (avoid with: ["attachment"] unless required) and/or add server-side filtering/pagination.

Example: enforce max bytes before parsing:

const MAX_BYTES = 2 * 1024 * 1024; // 2MB
const len = Number(response.headers.get("content-length") ?? "0");
if (len && len > MAX_BYTES) throw new Error("Response too large");

const reader = response.body?.getReader();
let received = 0;
const chunks: Uint8Array[] = [];
while (reader) {
  const { value, done } = await reader.read();
  if (done) break;
  received += value.byteLength;
  if (received > MAX_BYTES) throw new Error("Response too large");
  chunks.push(value);
}
const bodyBytes = chunks.length ? Buffer.concat(chunks) : null;
return new Response(bodyBytes, { status: response.status, headers: response.headers });

Also ensure catchup handles oversize responses gracefully (log + treat as unresolved) rather than crashing.

3. 🟡 BlueBubbles password may leak to logs via unsanitized error strings (URL query contains password)
Property Value
Severity Medium
CWE CWE-532
Location extensions/bluebubbles/src/catchup.ts:366-375

Description

buildBlueBubblesApiUrl embeds the BlueBubbles API password into the request URL query string (?password=...). The new catchup/monitor code logs errors using String(err).

This combination can leak credentials to logs because many network/HTTP errors (and wrapped errors) commonly include the full request URL in their message/stack.

Relevant points:

  • Secret in URL: buildBlueBubblesApiUrl sets url.searchParams.set("password", params.password).
  • Unsanitized logging: catchup and monitor now log String(err) directly.
  • Data flow: runBlueBubblesCatchup calls processMessage for each replayed message; processMessage performs downstream BlueBubbles API calls (attachments/history/chat queries) that use buildBlueBubblesApiUrl, so errors thrown from those code paths can propagate back and be logged here.

Vulnerable log sites added/changed in this diff:

error?.(`[${accountId}] BlueBubbles catchup: processMessage failed: ${String(err)}`);
runtime.error?.(`[${account.accountId}] BlueBubbles catchup: unexpected failure: ${String(err)}`);

Recommendation

Avoid ever placing secrets in URLs, and ensure all logged errors are scrubbed.

Preferred fix (remove secret from URL):

  • Change BlueBubbles authentication to use a header (e.g. Authorization or X-API-Key) instead of ?password=.
  • Update all call sites accordingly.

Defense-in-depth (redact before logging):
Implement a small redaction helper used by all BlueBubbles error logs:

function redactBlueBubblesSecrets(text: string): string {// redact password query param
  return text.replace(/([?&]password=)[^&#\s]*/gi, "$1<redacted>");
}// usage
error?.(
  `[${accountId}] BlueBubbles catchup: processMessage failed: ${redactBlueBubblesSecrets(String(err))}`,
);

Apply the same redaction to the monitor.ts catch block (and ideally other existing String(err) logs in this extension).

4. 🔵 Log injection via unsanitized BlueBubbles accountId in catchup/monitor logging
Property Value
Severity Low
CWE CWE-117
Location extensions/bluebubbles/src/catchup.ts:294-295

Description

The BlueBubbles extension logs the configured accountId directly into log/error messages without neutralizing control characters. If accountId contains newlines (\n, \r) or other control characters, an attacker (or less-trusted tenant/operator) who can influence config can forge additional log lines, spoof severity/prefixes, or hide malicious activity.

Evidence that account IDs may contain filesystem-unsafe characters (and thus are not guaranteed to be strictly alphanumeric) exists in the new catchup tests and cursor-path logic:

  • resolveCursorFilePath() replaces non [a-zA-Z0-9_-] chars, implying raw accountId may include other characters.
  • Tests use account IDs like "acct/a" and "acct:a".

Problematic logging patterns include:

  • error?.([${accountId}] ... ${String(err)}) in catchup.ts
  • runtime.error?.([${account.accountId}] ... ${String(err)}) in monitor.ts

Vulnerable code examples:

error?.(`[${accountId}] BlueBubbles catchup: cannot resolve server account: ${String(err)}`);
runtime.error?.(`[${account.accountId}] BlueBubbles catchup: unexpected failure: ${String(err)}`);

Recommendation

Neutralize untrusted values before writing them to logs (CWE-117), e.g. replace control characters and optionally cap length.

Example:

function sanitizeForLog(value: string): string {// Replace CR/LF/TAB and other ASCII control chars.
  return value.replace(/[\u0000-\u001F\u007F]/g, "_").slice(0, 128);
}

const accountTag = sanitizeForLog(accountId);
error?.(`[${accountTag}] BlueBubbles catchup: cannot resolve server account: ${String(err)}`);

Apply the same sanitization anywhere account.accountId is interpolated into log/error messages (including monitor.ts).


Analyzed PR: #66857 at commit 3d225e7

Last updated on: 2026-04-15T02:22:20Z

@openclaw-barnacle openclaw-barnacle Bot added channel: bluebubbles Channel integration: bluebubbles size: XL maintainer Maintainer-authored PR labels Apr 14, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 14, 2026

Greptile Summary

Adds a per-account startup catchup pass to the BlueBubbles channel that queries /api/v1/message/query?after=<cursor> and replays missed messages through the existing processMessage pipeline, closing the permanent-loss hole that existed when the gateway was unreachable during a BB Server webhook POST. The design is thorough: atomic cursor persistence, future-cursor recovery, truncation-aware page-boundary advancement, failure-held cursor, firstRunLookback clamped to maxAge, hard ceilings on lookback and message counts, and deduplication via #66816's inbound GUID cache — all covered by 21 scoped tests that pass alongside the full 410-test suite.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style/hardening suggestions that do not affect correctness.

No P0 or P1 issues found. The cursor logic, security surface (processMessage path unchanged, isFromMe double-filter, dedupe integration), and edge-case handling are well-implemented and well-tested. Two P2 observations: a changelog attribution capitalization nit, and a design note that a persistently-failing message permanently wedges the catchup cursor — a known, documented tradeoff that doesn't introduce incorrect behavior on the normal path.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: CHANGELOG.md
Line: 15

Comment:
**Changelog attribution capitalization**

The entry uses `thanks @omarshahine` (lowercase `t`) while every other attributed entry in this file uses `Thanks @author` (uppercase). Per the repo convention in CLAUDE.md: `prefer \`Thanks @author\``.

```suggestion
- BlueBubbles/webhook-catchup: replay missed webhook messages after gateway restart via a persistent per-account cursor and `/api/v1/message/query?after=<ts>` pass, so messages delivered while the gateway was down no longer disappear. Uses the existing `processMessage` path and is deduped by #66816's inbound GUID cache. (#66721) Thanks @omarshahine
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/bluebubbles/src/catchup.ts
Line: 338-345

Comment:
**Poison message permanently wedges cursor**

If `processMessage` throws consistently for a specific message (e.g., a malformed payload that never normalizes correctly), the cursor is pinned at `failureTs - 1` indefinitely. Every subsequent gateway startup re-fetches and retries the same message, emitting an error log each time with no backoff or escape hatch.

The PR's design rationale ("never lose a message") is sound, but an operator with a stuck message would have no automatic relief short of manually advancing or deleting the cursor file. A simple safeguard — such as logging the affected GUID prominently or noting the cursor file path in the error message — would reduce the time-to-diagnose when this situation occurs.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(bluebubbles): replay missed webhook..." | Re-trigger Greptile

Comment thread CHANGELOG.md Outdated
Comment thread extensions/bluebubbles/src/catchup.ts
@omarshahine
Copy link
Copy Markdown
Contributor Author

Review response

Pushed e33dd80b13 addressing the actionable findings; deferred findings filed as separate issues.

Fixed in this push

  • Aisle Login fails with 'WebSocket Error (socket hang up)' ECONNRESET #2 (Medium): cursor race condition → Added module-level singleflight map (inFlightCatchups: Map<accountId, Promise>) in runBlueBubblesCatchup. Concurrent calls for the same accountId now coalesce into a single in-flight run with a single cursor write. New test coalesces concurrent runs for the same accountId via in-process singleflight covers the behavior (asserts fetchCount=1, processCount=1, r1 === r2 across two concurrent calls).
  • Greptile P2 (CHANGELOG attribution casing)thanksThanks and added trailing period to match the rest of the Fixes section.

Deferred to separate issues

Tests

  • catchup.test.ts — 22/22 (added singleflight coverage)
  • Full BB suite — 411/411
  • pnpm check — green
  • src/security/temp-path-guard.test.ts — 3/3 (passes now that os.tmpdir was swapped for resolvePreferredOpenClawTmpDir, matching inbound-dedupe's existing pattern)
  • lint:tmp:no-random-messaging — clean

@omarshahine omarshahine force-pushed the lobster/bb-catchup-squash branch 2 times, most recently from 7a60224 to 9d1754c Compare April 15, 2026 02:10
…rt (#66721)

Adds an in-process startup catchup pass to the BlueBubbles channel that
queries BB Server for messages delivered since a persisted per-account
cursor and re-feeds each through the existing processMessage pipeline.

Fixes the missed-message hole documented in #66721: BB's WebhookService
is fire-and-forget on POST failure (no retries), and BB's MessagePoller
only re-fires webhooks on BB-side reconnection events (Messages.app /
APNs), not on webhook-receiver recovery. So inbound messages delivered
while the gateway was down, restarting, or wedged were permanently lost.

Design

- New extensions/bluebubbles/src/catchup.ts with
  fetchBlueBubblesMessagesSince (POSTs /api/v1/message/query with
  {after, sort:"ASC", with:[chat, chat.participants, attachment]}),
  load/saveBlueBubblesCatchupCursor (file-backed {lastSeenMs,
  updatedAt} per account under
  <stateDir>/bluebubbles/catchup/<accountId>__<hash>.json using the
  plugin-sdk's atomic JSON helpers, same state-dir root as
  inbound-dedupe via the canonical SDK resolver, and
  resolvePreferredOpenClawTmpDir for test isolation to satisfy the
  messaging-tmpdir and temp-path-guard lints), and
  runBlueBubblesCatchup orchestrator.
- monitor.ts: fire catchup as a background task after the webhook
  target registers; errors are logged but never block the
  channel-ready signal.
- config-schema.ts: new optional `catchup` block (enabled,
  maxAgeMinutes, perRunLimit, firstRunLookbackMinutes); defaults on
  with 2h lookback / 50 msg cap / 30-min first-run lookback.
- accounts.ts: adds `catchup` to nestedObjectKeys so per-account
  overrides deep-merge on top of channel-level defaults (mirroring
  the existing `network` precedent).

Safety

- Goes through the same processMessage path webhooks use, so auth,
  allowlist, pairing, and downstream agent dispatch apply unchanged.
- Dedupes against #66816's persistent inbound GUID cache.
- Never dispatches isFromMe records (checked before and after
  normalization).
- Runs once per gateway startup and does NOT skip on rapid restarts -
  skipping would permanently lose any messages that arrived during
  the brief downtime between two startups.
- Cursor advances to nowMs on full success, held at
  min(earliestFailureTs - 1, previousCursor) on any processMessage
  failure so retries pick up exactly the failed records, or at
  latestFetchedTs on truncation (fetchedCount === perRunLimit) so the
  next gateway startup picks up the unfetched tail.
- Future-dated cursor (NTP rollback, manual clock adjust) treated as
  unusable and recovered via firstRunLookback; cursor is repaired at
  end of run.
- First-run lookback clamped to the maxAge ceiling.
- Hard ceilings: 12h max lookback, 500 messages per run.
- Loud WARNING on perRunLimit truncation pointing at the config knob
  to raise.

Why this approach

The fix mirrors a workspace-level shell script that's been running on
a real OpenClaw install for ~4 weeks (~100 LoC of bash + python doing
the same query/filter/POST flow). Porting it into the BB channel
itself means every install gets recovery for free, calls
processMessage directly (no re-POST hop), and benefits from #66816's
persistent dedupe automatically.

Validation

- 21 scoped tests in extensions/bluebubbles/src/catchup.test.ts.
- Full BB suite 410/410.
- pnpm check green.
- src/security/temp-path-guard.test.ts and
  lint:tmp:no-random-messaging both pass (use
  resolvePreferredOpenClawTmpDir + string concatenation instead of
  os.tmpdir + template literal).
- Live E2E on macOS 26.3 / BB Server 1.9.x: 3/3 messages replayed.

Closes #66721.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@omarshahine omarshahine force-pushed the lobster/bb-catchup-squash branch from 9d1754c to 3d225e7 Compare April 15, 2026 02:20
@omarshahine omarshahine merged commit 6f1d321 into main Apr 15, 2026
26 checks passed
@omarshahine omarshahine deleted the lobster/bb-catchup-squash branch April 15, 2026 02:20
kvnkho pushed a commit to kvnkho/openclaw that referenced this pull request Apr 17, 2026
…rt (openclaw#66857)

Adds an in-process startup catchup pass to the BlueBubbles channel that
queries BB Server for messages delivered since a persisted per-account
cursor and re-feeds each through the existing processMessage pipeline.

Fixes the missed-message hole documented in openclaw#66721: BB's WebhookService
is fire-and-forget on POST failure, and MessagePoller only re-fires
webhooks on BB-side reconnection events, not on webhook-receiver
recovery.

- New extensions/bluebubbles/src/catchup.ts with singleflight per
  accountId, cursor persistence via the canonical state-paths
  resolver, bounded query (perRunLimit + maxAgeMinutes), failure-held
  cursor, truncation-aware page-boundary advancement, future-cursor
  recovery, isFromMe filter (pre- and post-normalization).
- monitor.ts fires catchup as a background task after the webhook
  target registers.
- config-schema.ts adds optional catchup block; accounts.ts adds
  catchup to nestedObjectKeys for deep-merge per-account overrides.
- Dedupes against openclaw#66816's persistent inbound GUID cache.
- 22 scoped tests; full BB suite 411/411; pnpm check green; live E2E
  on macOS 26.3 / BB Server 1.9.x recovered 3/3 missed messages.

Closes openclaw#66721.

Co-authored-by: Omar Shahine <omar@shahine.com>
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…rt (openclaw#66857)

Adds an in-process startup catchup pass to the BlueBubbles channel that
queries BB Server for messages delivered since a persisted per-account
cursor and re-feeds each through the existing processMessage pipeline.

Fixes the missed-message hole documented in openclaw#66721: BB's WebhookService
is fire-and-forget on POST failure, and MessagePoller only re-fires
webhooks on BB-side reconnection events, not on webhook-receiver
recovery.

- New extensions/bluebubbles/src/catchup.ts with singleflight per
  accountId, cursor persistence via the canonical state-paths
  resolver, bounded query (perRunLimit + maxAgeMinutes), failure-held
  cursor, truncation-aware page-boundary advancement, future-cursor
  recovery, isFromMe filter (pre- and post-normalization).
- monitor.ts fires catchup as a background task after the webhook
  target registers.
- config-schema.ts adds optional catchup block; accounts.ts adds
  catchup to nestedObjectKeys for deep-merge per-account overrides.
- Dedupes against openclaw#66816's persistent inbound GUID cache.
- 22 scoped tests; full BB suite 411/411; pnpm check green; live E2E
  on macOS 26.3 / BB Server 1.9.x recovered 3/3 missed messages.

Closes openclaw#66721.

Co-authored-by: Omar Shahine <omar@shahine.com>
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…rt (openclaw#66857)

Adds an in-process startup catchup pass to the BlueBubbles channel that
queries BB Server for messages delivered since a persisted per-account
cursor and re-feeds each through the existing processMessage pipeline.

Fixes the missed-message hole documented in openclaw#66721: BB's WebhookService
is fire-and-forget on POST failure, and MessagePoller only re-fires
webhooks on BB-side reconnection events, not on webhook-receiver
recovery.

- New extensions/bluebubbles/src/catchup.ts with singleflight per
  accountId, cursor persistence via the canonical state-paths
  resolver, bounded query (perRunLimit + maxAgeMinutes), failure-held
  cursor, truncation-aware page-boundary advancement, future-cursor
  recovery, isFromMe filter (pre- and post-normalization).
- monitor.ts fires catchup as a background task after the webhook
  target registers.
- config-schema.ts adds optional catchup block; accounts.ts adds
  catchup to nestedObjectKeys for deep-merge per-account overrides.
- Dedupes against openclaw#66816's persistent inbound GUID cache.
- 22 scoped tests; full BB suite 411/411; pnpm check green; live E2E
  on macOS 26.3 / BB Server 1.9.x recovered 3/3 missed messages.

Closes openclaw#66721.

Co-authored-by: Omar Shahine <omar@shahine.com>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…rt (openclaw#66857)

Adds an in-process startup catchup pass to the BlueBubbles channel that
queries BB Server for messages delivered since a persisted per-account
cursor and re-feeds each through the existing processMessage pipeline.

Fixes the missed-message hole documented in openclaw#66721: BB's WebhookService
is fire-and-forget on POST failure, and MessagePoller only re-fires
webhooks on BB-side reconnection events, not on webhook-receiver
recovery.

- New extensions/bluebubbles/src/catchup.ts with singleflight per
  accountId, cursor persistence via the canonical state-paths
  resolver, bounded query (perRunLimit + maxAgeMinutes), failure-held
  cursor, truncation-aware page-boundary advancement, future-cursor
  recovery, isFromMe filter (pre- and post-normalization).
- monitor.ts fires catchup as a background task after the webhook
  target registers.
- config-schema.ts adds optional catchup block; accounts.ts adds
  catchup to nestedObjectKeys for deep-merge per-account overrides.
- Dedupes against openclaw#66816's persistent inbound GUID cache.
- 22 scoped tests; full BB suite 411/411; pnpm check green; live E2E
  on macOS 26.3 / BB Server 1.9.x recovered 3/3 missed messages.

Closes openclaw#66721.

Co-authored-by: Omar Shahine <omar@shahine.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: bluebubbles Channel integration: bluebubbles maintainer Maintainer-authored PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BlueBubbles: replay missed webhook messages after gateway restart (cursor + fetchBlueBubblesHistory + processMessage)

1 participant