Skip to content

Bug: WhatsApp bridge /health reports 'connected' during Baileys WebSocket flapping (428/503 loop), causing silent message loss #63277

Description

@kk17

Bug: WhatsApp bridge /health reports connected during Baileys WebSocket flapping (428/503 reconnect loop), causing silent message loss

Summary

The WhatsApp bridge's /health endpoint reports connectionState: "connected" even when the Baileys WebSocket is in a rapid disconnect-reconnect loop (codes 428/503). Because the gateway relies on /health to determine platform liveness, it considers WhatsApp fully operational and never triggers the reconnection watcher. Messages sent by users during the brief disconnect windows are silently lost — neither received by the bridge nor flagged as missed.

Environment

  • Hermes version: v2026.7.7.2-247-g54e186457 (0.18.2)
  • Bridge: scripts/whatsapp-bridge/bridge.js, Baileys @whiskeysockets/baileys 7.0.0-rc13
  • Mode: WHATSAPP_MODE=bot
  • OS: Debian (Linux 6.12.30)

Problem

When WhatsApp's server starts sending disconnect codes (428 = precondition required, 503 = service unavailable), Baileys rapidly cycles through:

✅ WhatsApp connected!
⚠️ Connection closed (reason: 428). Reconnecting in 3s...
✅ WhatsApp connected!
⚠️ Connection closed (reason: 428). Reconnecting in 3s...

During the ~3-second disconnect windows, any user messages sent to the family group are not received by Baileys (the messages.upsert event never fires). These messages are permanently lost — WhatsApp delivers them to the user's phone but not to the bridge.

Root cause in bridge.js:

// Line 380
let connectionState = 'disconnected';

// Lines 425-448: connectionUpdate handler
if (connection === 'close') {
    connectionState = 'disconnected';   // Set to disconnected...
    // ... 3s later, startSocket() reconnects
} else if (connection === 'open') {
    connectionState = 'connected';      // ...then immediately back to connected
}

// Lines 1071-1077: health endpoint
app.get('/health', (req, res) => {
    res.json({
        status: connectionState,    // Reports "connected" during brief open windows
        queueLength: messageQueue.length,
        uptime: process.uptime(),
        scriptHash: SCRIPT_HASH,
    });
});

The gateway's poll loop (adapter.py line ~1233-1268) checks _check_managed_bridge_exit() but not the health status — it only checks whether the bridge process is alive, not whether the Baileys WebSocket is actually connected. And the platform reconnection watcher only runs for platforms that failed connect(), not for platforms that are flapping.

Observed impact

In a production setup (family meal-planning bot), the bridge entered a 428 loop at ~17:36 and continued until a manual gateway restart at 22:04. During this 4.5-hour window:

  • 0 messages were received by the gateway (confirmed via gateway logs)
  • The bridge /health reported connected intermittently
  • A critical user message (meal plan for the next day, sent at 19:50) was silently lost
  • The bridge produced no JSON log events for the entire period, only non-timestamped text output (Connection closed (reason: 428))
  • Session key files were still being updated by Baileys during reconnection attempts, making it appear the bridge was receiving data

Expected behavior

  1. The bridge should expose connection quality metrics, not just binary connected/disconnected
  2. The gateway should detect sustained flapping and either restart the bridge or mark the platform as degraded

Suggested fix

Option A: Bridge-side disconnect counter (minimal)

Add a rolling disconnect counter to bridge.js that tracks recent reconnections. When the count exceeds a threshold (e.g., >5 disconnects in 60 seconds), report a new health status:

let disconnectTimestamps = [];

// In connectionUpdate close handler:
disconnectTimestamps.push(Date.now());
disconnectTimestamps = disconnectTimestamps.filter(t => t > Date.now() - 60000);

// In /health:
const isFlapping = disconnectTimestamps.length > 5;
res.json({
    status: isFlapping ? 'degraded' : connectionState,
    disconnectCount60s: disconnectTimestamps.length,
    queueLength: messageQueue.length,
    uptime: process.uptime(),
    scriptHash: SCRIPT_HASH,
});

Option B: Gateway-side detection (complementary)

In adapter.py's _poll_messages() loop, periodically check /health for status === 'degraded' or track consecutive empty polls. If the bridge reports degraded for sustained periods, trigger a bridge restart via the existing _set_fatal_error mechanism.

Additional context

  • The 428/503 errors may indicate the WhatsApp Web session needs re-pairing, but this is a separate concern from the silent message loss.
  • The gateway already has a reconnection watcher for failed platforms, but it doesn't cover the "connected-but-flapping" case.
  • Bridge version: scriptHash: f6c1394bc6fa2984 (file hash of bridge.js)

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existscomp/pluginsPlugin system and bundled pluginsplatform/whatsappWhatsApp Business adaptersweeper:risk-message-deliverySweeper risk: may drop, duplicate, misroute, or suppress messagestype/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions