Skip to content

ARIA: discard stale outgoing WhatsApp messages on reconnect#348

Open
holoduke wants to merge 1 commit into
mainfrom
aria/whatsapp-stale-send-guard
Open

ARIA: discard stale outgoing WhatsApp messages on reconnect#348
holoduke wants to merge 1 commit into
mainfrom
aria/whatsapp-stale-send-guard

Conversation

@holoduke

Copy link
Copy Markdown
Owner

Summary

Fixes the offline-reconnect bug from 2026-05-20 ~12:15 UTC where ARIA sent stale outgoing messages (notably "How is it going" to Gillis and a DM to 31620510229) right after a brief offline period. The sends had been queued before the disconnect and were flushed verbatim on reconnect, with the original context long gone.

Changes

  • backend/integrations/whatsapp.ts

    • Added an internal pendingSendQueue of { jid, text, queuedAt, scheduled, resolve, reject }.
    • sendMessage(jid, text, options?) now queues with a timestamp when isConnected === false instead of throwing. The returned promise resolves/rejects once the queue is drained.
    • On connection.update"open", flushPendingSendQueue() runs: it discards any non-scheduled message where (now - queuedAt) > staleMsgThresholdMs, logs the discard with jid/age/preview, rejects the caller's promise with a descriptive error, and sends the rest.
    • Scheduled deliveries can opt out of the stale check by passing { scheduled: true }.
  • backend/brain-config.ts

    • New staleMsgThresholdMs: number field on BrainConfig (default 5 * 60 * 1000, env override STALE_MSG_THRESHOLD_MS). 5 minutes matches the think-tick interval — anything older than one tick is by definition stale context.

Why 5 minutes

Conversation context turns over roughly every think tick. A message that's been sitting in the queue for longer than that was almost certainly drafted in response to a state the recipient is no longer in.

Test plan

  • Backend typecheck (tsc --noEmit) passes — verified locally.
  • Simulate disconnect → enqueue message → wait > 5 min → reconnect → verify warn log and that the message is NOT delivered.
  • Simulate disconnect → enqueue → reconnect within 5 min → verify message IS delivered.
  • Confirm existing callers (brain-delivery.ts, owner-handler.ts, brain-ticks.ts) still handle rejected promises gracefully (all already wrap in try/catch).

🤖 Generated with Claude Code

…connect

When the WhatsApp socket drops and sendMessage is called while disconnected,
the call now queues the message instead of throwing. On 'open', the queue is
drained, but messages older than staleMsgThresholdMs (default 5 min, env
STALE_MSG_THRESHOLD_MS) are discarded and logged with reason. Scheduled
messages can opt out via { scheduled: true }.

On 2026-05-20 ~12:15-12:16 UTC, after a brief offline period, sends queued
from before the disconnect were flushed and arrived stale (e.g. "How is it
going" to Gillis), causing social damage. 5-min threshold matches the think
tick interval — anything older than one tick is by definition stale context.

Intent-summary: queued WhatsApp sends from before a disconnect were flushed on reconnect without checking their age, delivering messages with stale context to contacts.
Intent-tokens: whatsapp,stale,reconnect,queue,offline,flush,context

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant