Skip to content

fix(whatsapp): drain eligible pending deliveries on reconnect#63916

Merged
mcaxtr merged 2 commits into
mainfrom
fix/whatsapp-reconnect-pending-drain
Apr 10, 2026
Merged

fix(whatsapp): drain eligible pending deliveries on reconnect#63916
mcaxtr merged 2 commits into
mainfrom
fix/whatsapp-reconnect-pending-drain

Conversation

@mcaxtr

@mcaxtr mcaxtr commented Apr 9, 2026

Copy link
Copy Markdown
Member

Summary

  • Problem: WhatsApp reconnect replay only retried queue entries already marked with No active WhatsApp Web listener, so fresh pending WhatsApp entries stayed stuck until the next startup.
  • Why it matters: user-facing outbound messages created during a reconnect window could remain in delivery-queue/ even after WhatsApp came back.
  • What changed: moved reconnect selection policy into the WhatsApp monitor and widened reconnect drain to same-account pending WhatsApp entries that are eligible to retry now, while still bypassing backoff for the no-listener case.
  • What did NOT change (scope boundary): this does not add a new queue kind, new config, or payload-text heuristics for filtering internal messages.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: reconnect replay policy was hardcoded to only match WhatsApp entries whose lastError already contained the no-listener failure, so fresh pending entries and ordinary retry-eligible entries were invisible to reconnect recovery.
  • Missing detection / guardrail: reconnect coverage only locked in the no-listener path, not the broader pending-but-deliverable-after-reconnect behavior.
  • Contributing context (if known): the previous reconnect fix was intentionally narrow to avoid replaying too much on every reconnect.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/infra/outbound/delivery-queue.reconnect-drain.test.ts
  • Scenario the test should lock in: reconnect drains fresh pending same-account WhatsApp entries, drains backoff-eligible retries, still bypasses backoff for no-listener failures, and still respects backoff for ordinary transient failures.
  • Why this is the smallest reliable guardrail: the bug sits at the seam between persisted queue state, retry policy, and the reconnect trigger; smaller helper-only tests miss that interaction.
  • Existing test that already covers this (if any): the existing reconnect-drain tests already covered the no-listener path and the startup/reconnect duplicate-send guard.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

Queued WhatsApp messages for the reconnecting account now replay after reconnect if they are already eligible to retry, even when they were never attempted before. The previous immediate-retry behavior for No active WhatsApp Web listener failures remains intact.

Diagram (if applicable)

Before:
[pending WhatsApp queue entry] -> [reconnect] -> [only no-listener-marked entries drain] -> [fresh pending entry stays stuck]

After:
[pending WhatsApp queue entry] -> [reconnect] -> [WhatsApp account-scoped eligibility check] -> [deliver now if retry-eligible]

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: Linux
  • Runtime/container: Node 22 + pnpm workspace run
  • Model/provider: N/A
  • Integration/channel (if any): WhatsApp Web
  • Relevant config (redacted): default WhatsApp account linked; local gateway on loopback

Steps

  1. Seed a pending WhatsApp queue entry for the default account while the gateway is running.
  2. Force or wait for a WhatsApp reconnect, then let the monitor re-register the listener.
  3. Observe whether the queued message is delivered and removed from delivery-queue/ without another gateway restart.

Expected

  • Same-account pending WhatsApp entries that are already eligible to retry are delivered after reconnect.
  • No-listener failures still replay immediately on reconnect.

Actual

  • Before this fix, only entries with the explicit no-listener failure drained on reconnect; fresh pending entries remained stuck until startup recovery ran again.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: manually seeded a fresh pending WhatsApp queue entry, reduced the watchdog interval locally for faster testing, triggered reconnect, and confirmed the queued message was delivered after reconnect on this branch.
  • Edge cases checked: no-listener failures still bypass backoff; ordinary transient failures still respect backoff; non-WhatsApp entries are ignored; the startup/reconnect duplicate-send guard stays covered by tests.
  • What you did not verify: live multi-account WhatsApp behavior and any future explicit do-not-replay policy for specific delivery kinds.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Risks and Mitigations

  • Risk: reconnect now drains a broader set of pending WhatsApp entries than before.
    • Mitigation: selection is still limited to the reconnecting WhatsApp account, still uses generic retry eligibility/backoff, and still shares the per-entry recovery claim guard with startup recovery.

@aisle-research-bot

aisle-research-bot Bot commented Apr 9, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

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

# Severity Title
1 🟠 High Untrusted plugin can drain and resend arbitrary queued deliveries via exported drainPendingDeliveries
2 🟡 Medium Stale backoff-bypass decision in drainPendingDeliveries can cause rapid retry storms
3 🟡 Medium Path traversal via unsanitized delivery queue entry id in loadPendingDelivery/ackDelivery/failDelivery/moveToFailed
4 🔵 Low Log injection via untrusted drainKey/logLabel interpolation in drainPendingDeliveries()
1. 🟠 Untrusted plugin can drain and resend arbitrary queued deliveries via exported drainPendingDeliveries
Property Value
Severity High
CWE CWE-284
Location src/plugin-sdk/infra-runtime.ts:79-80

Description

drainPendingDeliveries is re-exported from the public plugin SDK (src/plugin-sdk/infra-runtime.ts), giving plugin code a generic primitive that:

  • Iterates over all pending deliveries (loadPendingDeliveries) and lets the caller decide which ones to drain via a caller-supplied selectEntry predicate.
  • Can optionally bypass retry backoff (bypassBackoff) and immediately re-deliver entries.
  • Uses a caller-supplied drainKey to gate concurrency; a malicious plugin can choose colliding keys to block other drains.

If third-party plugins are not fully trusted (or are intended to be capability-scoped), this expands the attack surface:

  • Broken isolation / cross-channel impact: a plugin can match entries outside its own channel/account and force delivery attempts.
  • Message flooding / cost amplification: bypassing backoff allows rapid repeated delivery attempts.
  • DoS / operational interference: drainKey collisions can prevent legitimate drains from running.

Vulnerable exports/usage:

// Public SDK re-export
export { drainPendingDeliveries };
const matchingEntries = (await loadPendingDeliveries(opts.stateDir))
  .map((entry) => ({ entry, decision: opts.selectEntry(entry, now) }))
  .filter((item) => item.decision.match);

if (!decision.bypassBackoff) {
  const retryEligibility = isEntryEligibleForRecoveryRetry(currentEntry, Date.now());
  ...
}

Recommendation

Do not expose a generic queue-draining primitive to plugins unless plugins are fully trusted. If plugins are intended to be capability-scoped, add guardrails:

  • Remove drainPendingDeliveries from the public plugin SDK and keep it internal, or expose a narrow, channel-scoped API that enforces channel/account constraints server-side.
  • If an SDK export is required, enforce that the caller can only drain entries for its own channel/account (e.g., pass an enforced channel/accountId scope rather than a free-form predicate).
  • Remove/disable bypassBackoff for plugin callers, or gate it behind an internal-only token/capability.
  • Namespace and validate drainKey (e.g., host-generated key), and consider per-plugin rate limiting.

Example of a safer, scoped API surface:

export async function drainChannelAccountDeliveries(opts: {
  channel: "whatsapp";
  accountId: string;
  ...
}) {
  return drainPendingDeliveries({
    drainKey: `plugin:${opts.channel}:${opts.accountId}`,
    logLabel: `${opts.channel} drain`,
    ...,
    selectEntry: (entry) => ({
      match: entry.channel === opts.channel && entry.accountId === opts.accountId,// never allow bypass from plugins
      bypassBackoff: false,
    }),
  });
}
2. 🟡 Stale backoff-bypass decision in drainPendingDeliveries can cause rapid retry storms
Property Value
Severity Medium
CWE CWE-670
Location src/infra/outbound/delivery-queue-recovery.ts:238-299

Description

drainPendingDeliveries() computes decision = selectEntry(entry, now) from an initial snapshot of queued entries, then re-reads the entry from disk as currentEntry after claiming it.

However, the function continues to use the stale decision.bypassBackoff when deciding whether to enforce retry backoff for currentEntry:

  • decision.bypassBackoff is derived from snapshot fields (commonly entry.lastError) before the claim.
  • Between snapshot creation and loadPendingDelivery(...), another recovery path can update retryCount, lastAttemptAt, or lastError.
  • If the snapshot decision indicates bypassBackoff: true, the code skips isEntryEligibleForRecoveryRetry(currentEntry, ...) entirely, potentially causing immediate retries even though currentEntry is within its backoff window.

This can create unintended aggressive retry loops during reconnect/startup, leading to self-induced denial of service (CPU/network churn) and possible upstream rate-limit bans.

Recommendation

Recompute the drain decision after reloading currentEntry (and using a fresh now) and base the backoff-bypass decision on the same entry that will actually be delivered.

For example:

const currentEntry = await loadPendingDelivery(entry.id, opts.stateDir);
if (!currentEntry) return;

const now2 = Date.now();
const decision2 = opts.selectEntry(currentEntry, now2);
if (!decision2.match) return; // entry no longer matches criteria

if (!decision2.bypassBackoff) {
  const retryEligibility = isEntryEligibleForRecoveryRetry(currentEntry, now2);
  if (!retryEligibility.eligible) return;
}

Additionally, consider making bypassBackoff depend on immutable/claimed state (or verifying currentEntry.lastError still matches the bypass condition) to avoid bypass based on stale error strings.

3. 🟡 Path traversal via unsanitized delivery queue entry id in loadPendingDelivery/ackDelivery/failDelivery/moveToFailed
Property Value
Severity Medium
CWE CWE-22
Location src/infra/outbound/delivery-queue-storage.ts:50-62

Description

The delivery queue storage builds file paths by directly concatenating the caller-provided id into a filename and joining it to the queue directory:

  • resolveQueueEntryPaths(id) uses path.join(queueDir, ${id}.json) and ${id}.delivered with no validation
  • path.join will normalize .. segments, so an id like ../../otherdir/secret escapes queueDir
  • loadPendingDelivery then performs filesystem operations on the resulting path (stat, readFile, and potentially writeFile during migration), enabling arbitrary file read and, in some flows, file overwrite/clobber if an attacker can call these APIs with a crafted id

Vulnerable code:

function resolveQueueEntryPaths(id: string, stateDir?: string) {
  const queueDir = resolveQueueDir(stateDir);
  return {
    jsonPath: path.join(queueDir, `${id}.json`),
    deliveredPath: path.join(queueDir, `${id}.delivered`),
  };
}

export async function loadPendingDelivery(id: string, stateDir?: string) {
  const { jsonPath } = resolveQueueEntryPaths(id, stateDir);
  const stat = await fs.promises.stat(jsonPath);// ... readQueueEntry(jsonPath) ...// ... writeQueueEntry(jsonPath, entry) on migration ...
}

While current internal callers appear to use IDs generated by generateSecureUuid(), the functions are exported (via src/infra/outbound/delivery-queue.ts), so any untrusted caller/plugin that can reach these exports could supply a malicious id.

Recommendation

Constrain id to a safe filename and enforce that resolved paths stay within queueDir.

  1. Validate id against an allowlist (e.g., UUID v4):
const SAFE_ID_RE = /^[a-f0-9-]{36}$/i;
if (!SAFE_ID_RE.test(id)) throw new Error("Invalid queue entry id");
  1. Additionally, enforce containment using path.resolve:
const queueDir = resolveQueueDir(stateDir);
const jsonPath = path.resolve(queueDir, `${id}.json`);
if (!jsonPath.startsWith(path.resolve(queueDir) + path.sep)) {
  throw new Error("Path traversal");
}
  1. If you want to harden against symlink tricks inside the queue directory, prefer lstat and refuse symlinks, or open files with O_NOFOLLOW (where available) before reading/writing.

Apply the same validation/containment in all functions that accept an id (ackDelivery, failDelivery, moveToFailed, loadPendingDelivery).

4. 🔵 Log injection via untrusted drainKey/logLabel interpolation in drainPendingDeliveries()
Property Value
Severity Low
CWE CWE-117
Location src/infra/outbound/delivery-queue-recovery.ts:231-257

Description

drainPendingDeliveries() interpolates opts.drainKey (and opts.logLabel) directly into log message strings.

  • drainKey is caller-provided. In the WhatsApp reconnect path it is derived from accountId with only .trim() normalization.
  • If an attacker can influence accountId (e.g., via configuration, provisioning API, or any external input path that ultimately sets the account id), they may include control characters such as \r/\n to forge additional log lines, obscure true log origin, or inject misleading content.
  • This is particularly relevant when logs are used for incident response/auditing.

Vulnerable code:

opts.log.info(`${opts.logLabel}: already in progress for ${opts.drainKey}, skipping`);
...
opts.log.info(`${opts.logLabel}: ${matchingEntries.length} pending message(s) matched ${opts.drainKey}`);

Recommendation

Neutralize untrusted values before placing them into log message text, or log them as structured fields so the logger/runtime can escape them.

Option A: sanitize to a safe printable subset (strip control chars):

function sanitizeForLog(value: string): string {
  return value.replace(/[\r\n\t\0\f\v]/g, " ");
}

const drainKeySafe = sanitizeForLog(opts.drainKey);
const labelSafe = sanitizeForLog(opts.logLabel);
opts.log.info(`${labelSafe}: already in progress for ${drainKeySafe}, skipping`);

Option B: structured logging (preferred when supported):

(opts.log as any).info?.({ drainKey: opts.drainKey }, `${opts.logLabel}: already in progress, skipping`);

Also consider constraining accountId/drainKey to an allowlist character set (e.g., /^[a-z0-9._-]+$/i).


Analyzed PR: #63916 at commit 125e073

Last updated on: 2026-04-10T02:26:19Z

@openclaw-barnacle openclaw-barnacle Bot added channel: whatsapp-web Channel integration: whatsapp-web size: M maintainer Maintainer-authored PR labels Apr 9, 2026
@greptile-apps

greptile-apps Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR widens the WhatsApp reconnect drain to cover all same-account pending entries that are already eligible to retry — not just those explicitly marked with the "no listener" error — while preserving the immediate-replay behaviour for the no-listener case. The policy is now owned by the WhatsApp monitor via a selectEntry callback passed to the new generic drainPendingDeliveries, with the old drainReconnectQueue kept in infra-runtime.ts as a @deprecated compatibility shim. Test coverage is solid and covers every scenario from the PR's regression test plan.

Confidence Score: 5/5

Safe to merge; the only finding is a minor SDK type-export gap that doesn't affect runtime behavior or existing callers.

All remaining findings are P2. The logic is correct: concurrency guards carry over, the per-entry re-read after claiming prevents stale double-send, backoff bypass is properly scoped to the no-listener case, and the new test cases lock in every scenario from the regression plan. The prior P1 concern (unreachable drainReconnectQueue) has been resolved by moving it to infra-runtime.ts as a documented compatibility shim.

src/plugin-sdk/infra-runtime.ts — the newly public drainPendingDeliveries export lacks companion type re-exports.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/plugin-sdk/infra-runtime.ts
Line: 79

Comment:
**Missing companion type exports for `drainPendingDeliveries`**

`drainPendingDeliveries` is exported as a public SDK symbol, but the types a caller needs to annotate their `selectEntry` callback — `PendingDeliveryDrainDecision`, `QueuedDelivery`, `RecoveryLogger`, and `DeliverFn` — are not re-exported from this file. TypeScript inference covers the common case (inline callback with no explicit annotation), but any plugin that wants to define the callback separately or assign it to a typed variable will have no way to import the required types from the public SDK surface.

Consider adding the missing re-exports alongside the function:

```typescript
export { drainPendingDeliveries };
export type {
  DeliverFn,
  PendingDeliveryDrainDecision,
  QueuedDelivery,
  RecoveryLogger,
} from "../infra/outbound/delivery-queue.js";
```

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

Reviews (2): Last reviewed commit: "fix(plugin-sdk): restore drainReconnectQ..." | Re-trigger Greptile

Comment thread src/infra/outbound/delivery-queue-recovery.ts Outdated
Comment thread src/infra/outbound/delivery-queue.reconnect-drain.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: 94d93b31b5

ℹ️ 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/whatsapp/src/auto-reply/monitor.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: 13c95751d1

ℹ️ 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/whatsapp/src/auto-reply/monitor.ts
Comment thread src/plugin-sdk/infra-runtime.ts Outdated
@mcaxtr

mcaxtr commented Apr 10, 2026

Copy link
Copy Markdown
Member Author

Update after review pass:

What is now done in this PR:

  • widened WhatsApp reconnect replay to same-account pending entries that are eligible to retry, while keeping the immediate bypass for No active WhatsApp Web listener
  • moved the active reconnect policy into extensions/whatsapp/src/auto-reply/monitor.ts so core owns generic replay mechanics and WhatsApp owns reconnect selection
  • removed the old WhatsApp-specific reconnect policy from core recovery code
  • fixed the startup/reconnect stale-snapshot duplicate race by making both recovery paths claim first and then re-read the current queue entry from disk before retry/send decisions
  • added regression coverage for that stale-snapshot replay case
  • removed raw recipient identifiers from recovery/drain success logs
  • aligned the reconnect-drain test helper normalization with the monitor (.trim() || "default")
  • restored drainReconnectQueue as a backward-compatible plugin-sdk compatibility shim so this does not remove an existing public SDK symbol

What I explicitly decided not to fix in this PR:

  • the possible first-send/in-flight duplicate raised by Codex. I investigated it and agree it is plausible, but I do not have a concrete repro for that path. The straightforward fixes introduce new queue ownership / in-flight state semantics with their own failure modes, so I did not want to widen this PR into a speculative state machine change.
  • Aisles plugin-sdk capability concern around drainPendingDeliveries. That reads as a broader trust-model / plugin-boundary question rather than a concrete bug demonstrated by this change.
  • Aisles queue-id path traversal concern. It is technically worth a separate hardening look, but it depends on a maliciously modified queue file / local attacker / already-compromised writer and is not the main real-world risk introduced by this PR.

Scoped verification run after the fixes above:

  • pnpm test src/infra/outbound/delivery-queue.reconnect-drain.test.ts
  • pnpm test src/infra/outbound/delivery-queue.recovery.test.ts
  • pnpm test src/plugins/runtime-plugin-boundary.whatsapp.test.ts
  • pnpm test extensions/whatsapp/src/send.test.ts
  • pnpm test extensions/whatsapp/src/active-listener.test.ts
  • pnpm build

@codex review
@greptile review

@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: ea244d1f5b

ℹ️ 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/whatsapp/src/auto-reply/monitor.ts
@mcaxtr mcaxtr force-pushed the fix/whatsapp-reconnect-pending-drain branch from ea244d1 to 125e073 Compare April 10, 2026 02:24

@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: 125e0739fb

ℹ️ 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 on lines +283 to +285
match:
entry.channel === "whatsapp" &&
normalizeReconnectAccountId(entry.accountId) === normalizedAccountId,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Skip fresh queue entries during reconnect replay

This selector now matches every same-account WhatsApp queue row, including first-attempt entries that have never failed. Fresh evidence in this commit is src/infra/outbound/delivery-queue.reconnect-drain.test.ts adding coverage that explicitly drains “fresh pending WhatsApp entries,” which confirms retryCount=0 rows are replay candidates. Since deliverOutboundPayloads writes the queue row before send and only acks after send completion (src/infra/outbound/deliver.ts), a reconnect that happens while that first send is still in flight can trigger drainPendingDeliveries to send the same payload a second time, causing duplicate outbound messages.

Useful? React with 👍 / 👎.

@mcaxtr mcaxtr merged commit 95d4673 into main Apr 10, 2026
32 of 33 checks passed
@mcaxtr mcaxtr deleted the fix/whatsapp-reconnect-pending-drain branch April 10, 2026 02:41
steipete pushed a commit that referenced this pull request Apr 10, 2026
* fix(whatsapp): drain eligible pending deliveries on reconnect

* docs(changelog): note whatsapp reconnect pending drain
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…aw#63916)

* fix(whatsapp): drain eligible pending deliveries on reconnect

* docs(changelog): note whatsapp reconnect pending drain
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…aw#63916)

* fix(whatsapp): drain eligible pending deliveries on reconnect

* docs(changelog): note whatsapp reconnect pending drain
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…aw#63916)

* fix(whatsapp): drain eligible pending deliveries on reconnect

* docs(changelog): note whatsapp reconnect pending drain
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…aw#63916)

* fix(whatsapp): drain eligible pending deliveries on reconnect

* docs(changelog): note whatsapp reconnect pending drain
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…aw#63916)

* fix(whatsapp): drain eligible pending deliveries on reconnect

* docs(changelog): note whatsapp reconnect pending drain
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: whatsapp-web Channel integration: whatsapp-web maintainer Maintainer-authored PR size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant