Skip to content

fix(whatsapp): exponential reconnect backoff to prevent 405 spam loop - #119

Merged
exiao merged 8 commits into
live-configfrom
fix/whatsapp-bridge-reconnect-backoff
Jul 16, 2026
Merged

fix(whatsapp): exponential reconnect backoff to prevent 405 spam loop#119
exiao merged 8 commits into
live-configfrom
fix/whatsapp-bridge-reconnect-backoff

Conversation

@exiao

@exiao exiao commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Problem

The Baileys WhatsApp bridge retried every non-logout socket close at a flat 3s forever. When WhatsApp rejects the handshake with reason 405 (stale/outdated client protocol version), the socket closes and reconnects on a 3s loop with no ceiling.

Hit live on the BloomBot prod VPS: a stale hardcoded protocol version produced 11,708 consecutive 405 reconnects (~1,200 req/hr to WhatsApp's socket endpoint) — exactly the traffic pattern that gets a WhatsApp account flagged/banned as an abusive client.

Fix

Replace the flat reason === 515 ? 1000 : 3000 reconnect delay:

  • 515 (post-pairing restart): unchanged fast path (1s), resets the attempt counter — legitimate WhatsApp-requested restart.
  • Everything else (405, drops): exponential backoff + jitter, min(3000 * 2^(n-1), 300000) + rand(0..1000)ms, capped at 5 min. Worst case: ~1 req/3s → ~1 req/5min.
  • Reset reconnectAttempts = 0 on successful connection === 'open'.

Why this PR

The fix was applied live on prod to stop the ban risk immediately, but a live edit is wiped by the next runtime git pull — the committed base still has the flat-3s loop. Without this PR the bug resurrects on the next deploy.

Verification

  • node --check scripts/whatsapp-bridge/bridge.js → syntax OK
  • Live on prod: patched bridge running, /health = connected, fresh log ends at ✅ WhatsApp connected! with 0 new 405s after QR scan.

Patch note: ~/.hermes/plans/hermes-patches/whatsapp-bridge-reconnect-backoff.md

The bridge retried every non-logout socket close at a flat 3s forever. A
stale-protocol-version 405 rejection produced 11,708 consecutive reconnects
(~1200 req/hr) on prod, the traffic pattern that risks a WhatsApp account ban.

Non-515 closes now use exponential backoff + jitter capped at 5 min; the 515
post-pairing fast path (1s) is preserved and the attempt counter resets on a
successful connection open.

Patch note: ~/.hermes/plans/hermes-patches/whatsapp-bridge-reconnect-backoff.md
@claude

claude Bot commented Jul 15, 2026

Copy link
Copy Markdown

Claude finished @exiao's task in 1m 27s —— View job


Review: PR #119 — WhatsApp reconnect backoff

  • Read the diff vs origin/live-config
  • Trace reconnectPlan logic in bridge_helpers.js
  • Verify tests cover the policy
  • Post review

LGTM. The final reconnectPlan refactor resolves every prior thread and the policy is correct:

  • 515 → flat 1s, resets both reconnectAttempts and handshakeFailures (fast path intact).
  • First non-515 retry → flat 3s (at nextAttempts=1, backoff=3000 so the jitter window collapses to [3000,3000]).
  • Repeated failures → uniform jitter in [3s, 5min]; exponent = min(nextAttempts-1, 10) caps the shift so 2**exponent never overflows to Infinity.
  • 12h hard backoff correctly scoped to the 11th consecutive 405handshakeFailures is a 405-only streak (reason === 405 ? +1 : 0), so mixed transient+405 failures no longer trip it early. The dedicated test (first 405 after 10 transient closes → delay 3000) locks this in.
  • connection === 'open' resets both counters.

Tests are invariant/behavior-based (relate reason+attempt → delay class) rather than snapshots — good. Traced all six reconnectPlan cases by hand and the asserted delays match (e.g. attempts=9,405,random=0.5 → 3000 + floor(0.5*297001) = 151500 ✓).

This is a standalone Baileys bridge subprocess, so the agent-core invariants (prompt-cache stability, role alternation, narrow-waist tools, .env vs config.yaml) don't apply — no config/env/HERMES_HOME surface touched.

Minor (non-blocking):

  • RECONNECT_GIVEUP_AFTER is duplicated (bridge.js:381 and bridge_helpers.js). They agree at 10 today so behavior is correct, but the helper already owns the give-up decision — bridge.js could branch its log on plan.delay === RECONNECT_LONG_MS instead of re-deriving the threshold, avoiding a future drift where the log and the actual delay disagree.
  • By design, non-405 persistent failures retry at the 5min cap indefinitely (never give up) — intentional, flagging only for awareness.

Could not run node scripts/whatsapp-bridge/bridge.native.test.mjs locally (command execution not permitted in this run), but the assertions were verified by hand.

0 blocking, 2 minor.

@claude claude 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.

LGTM — exponential backoff + jitter math is correct (3s·2^(n-1), capped at 5min), counter resets on both connection === 'open' and the 515 fast path, and jitter breaks the bot-like fixed cadence. Standalone Baileys bridge process, so agent-core invariants (prompt-cache, role alternation, narrow-waist tools) don't apply; no config.yaml/env changes. Non-blocking note: the 515 path stays flat 1s with the counter reset (matches original intent, out of scope). 0 blocking.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces exponential backoff with jitter for WhatsApp socket reconnection attempts to prevent hammering servers during persistent failures. The review feedback suggests capping the exponent in the backoff calculation to prevent potential arithmetic overflow when the number of reconnection attempts becomes very large.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread scripts/whatsapp-bridge/bridge.js Outdated
exiao added 2 commits July 15, 2026 18:40
Spread each reconnect uniformly over 0..backoff instead of a fixed +0-1s, so
retries never settle into a regular machine cadence a bot-detector could flag.
Replace the exponential backoff with a fresh uniform-random pick in [3min,30min]
on every non-515 reconnect. Deliberately non-monotonic (e.g. 3m,15m,4m,30m) so
the cadence looks nothing like a machine, per Eric's request. Drops a persistent
failure from ~1200 retries/hr to ~2/hr.

@claude claude 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.

LGTM — exponential backoff + jitter math is correct, counter resets on both the 515 fast path and connection === 'open', and Gemini's overflow note is a non-bug in practice (Math.min caps backoff to 300000 before it feeds Math.random, so no Infinity/NaN). Standalone Baileys bridge process, so agent-core invariants don't apply. One minor doc-only note: the 'full jitter (0..backoff)' comment overstates the code, which is actually 3000 + rand(0..backoff). 0 blocking, 1 minor.

Count consecutive non-515 reconnect failures; the first 10 use the random
3-30min delay, and attempt 11+ waits a full 12h each cycle. A successful connect
resets the counter. Stops a persistent failure (stale client / banned handshake)
from retrying indefinitely and risking the account.

@claude claude 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.

LGTM — no blocking issues. Uniform-random 3-30min reconnect delay math is correct and the 515 fast path is intact. Two minor notes (posted in detail on the PR comment): (1) the block comment at bridge.js:438-441 still says 'exponential backoff + jitter' but the code now does a flat uniform-random pick — stale doc; (2) the 3-min floor applies to all transient drops, so ordinary network blips now incur a 3-30min outage instead of ~3s — intentional anti-ban tradeoff, flagging for awareness. Standalone Baileys bridge, no agent-core/config impact.

@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: 9179e7ed64

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/whatsapp-bridge/bridge.js Outdated

@claude claude 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.

LGTM — reviewed the current reconnect logic in scripts/whatsapp-bridge/bridge.js:440-473. Uniform-random [3min,30min] backoff + 12h hard backoff after 10 failures correctly breaks the 405 spam loop; counter resets on connection open and the 515 fast path is intact. Standalone Baileys subprocess, no agent-core/config/env impact. 3 minor non-blocking notes posted on the PR comment (3-min floor now hits benign transient drops; stale 'exponential' comment; cosmetic off-by-one in the give-up log). 0 blocking.

Comment thread scripts/whatsapp-bridge/bridge.js

@claude claude 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.

LGTM — reconnectPlan refactor resolves all prior blocking concerns: 515 resets the counter, first non-515 retry is a flat 3s, exponent is capped so no overflow. Capped exponential jitter [3s,5min] → hard 12h backoff on the 11th failure; connection=open resets. Standalone Baileys subprocess, no agent-core/config/env impact. 2 minor non-blocking notes on the PR comment (duplicated RECONNECT_GIVEUP_AFTER constant; ~21min-to-12h-backoff window affects benign long outages). 0 blocking.

@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: 1f994a60d3

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/whatsapp-bridge/bridge_helpers.js Outdated
Comment thread scripts/whatsapp-bridge/bridge.js Outdated

@claude claude 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.

LGTM — traced reconnectPlan against all five native-test cases: 515 resets the counter, first non-515 retry is a flat 3s, repeated failures use capped exponential jitter [3s,5min] (exponent capped, no overflow), and the 12h hard backoff is now scoped to persistent 405 handshake rejections only. Resolves every prior review thread. 2 minor non-blocking notes on the PR comment (duplicated RECONNECT_GIVEUP_AFTER constant; non-405 persistent failures retry at the 5min cap indefinitely). Standalone Baileys subprocess — no agent-core/config/env impact.

@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: 2864bafc3a

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/whatsapp-bridge/bridge_helpers.js Outdated

@claude claude 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.

LGTM — traced reconnectPlan against all native test cases. The latest commit (track handshake failure streaks) resolves the last Codex thread: handshakeFailures is now a 405-specific streak counter, separate from reconnectAttempts, so mixed transient+405 failures no longer trigger the 12h backoff early. Policy is correct: 515 fast path resets counters, first non-515 retry is flat 3s, repeated failures use capped exponential jitter [3s,5min] (exponent capped, no overflow), 11th consecutive 405 enters 12h hard backoff, connection=open resets. Standalone Baileys subprocess — no agent-core/config/env impact. 0 blocking, 2 minor (duplicated RECONNECT_GIVEUP_AFTER constant; non-405 persistent failures retry at 5min cap indefinitely by design).

@claude claude 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.

LGTM — reconnectPlan is correct: 515 resets both counters (flat 1s), first non-515 retry is flat 3s, repeated failures use capped exponential jitter in [3s,5min] (exponent capped, no overflow), and the 12h hard backoff is scoped to the 11th consecutive 405 via a 405-only handshakeFailures streak. connection=open resets both counters. Tests are invariant-based and cover all cases including mixed transient+405. Standalone Baileys subprocess — no agent-core/config/env impact. Minor: RECONNECT_GIVEUP_AFTER is duplicated across bridge.js and bridge_helpers.js (agree at 10 today). 0 blocking, 2 minor.

@exiao
exiao merged commit a4e244e into live-config Jul 16, 2026
31 checks passed
@exiao
exiao deleted the fix/whatsapp-bridge-reconnect-backoff branch July 16, 2026 19:03
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