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
- The bridge should expose connection quality metrics, not just binary connected/disconnected
- 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)
Bug: WhatsApp bridge
/healthreportsconnectedduring Baileys WebSocket flapping (428/503 reconnect loop), causing silent message lossSummary
The WhatsApp bridge's
/healthendpoint reportsconnectionState: "connected"even when the Baileys WebSocket is in a rapid disconnect-reconnect loop (codes 428/503). Because the gateway relies on/healthto 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
v2026.7.7.2-247-g54e186457(0.18.2)scripts/whatsapp-bridge/bridge.js, Baileys@whiskeysockets/baileys7.0.0-rc13WHATSAPP_MODE=botProblem
When WhatsApp's server starts sending disconnect codes (428 = precondition required, 503 = service unavailable), Baileys rapidly cycles through:
During the ~3-second disconnect windows, any user messages sent to the family group are not received by Baileys (the
messages.upsertevent 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:
The gateway's poll loop (
adapter.pyline ~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 failedconnect(), 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:
/healthreportedconnectedintermittentlyConnection closed (reason: 428))Expected behavior
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:
Option B: Gateway-side detection (complementary)
In
adapter.py's_poll_messages()loop, periodically check/healthforstatus === 'degraded'or track consecutive empty polls. If the bridge reports degraded for sustained periods, trigger a bridge restart via the existing_set_fatal_errormechanism.Additional context
scriptHash: f6c1394bc6fa2984(file hash of bridge.js)