-
-
Notifications
You must be signed in to change notification settings - Fork 69.4k
WhatsApp message tool fails with 'No active WhatsApp Web listener' while auto-reply works — duplicate listener Maps across bundled chunks #14406
Description
Summary
The message tool (used by agents for proactive WhatsApp sends) intermittently fails with:
Error: No active WhatsApp Web listener (account: default).
…while the auto-reply path (responding to inbound messages) works perfectly at the same time. This means the WhatsApp connection is alive, but the message tool cannot find it.
Root Cause Analysis
The WhatsApp active-listener registry (src/web/active-listener.ts) maintains an in-memory Map<string, Listener> at module scope. Due to Rollup code-splitting, this module is duplicated across multiple output chunks:
reply-B_4pVbIX.js— containssendMessageWhatsApp()(used by themessagetool viarunMessageAction)extensionAPI.js— containsmonitorWebChannel(),deliverWebReply(), heartbeat, and inbound message handlingloader-n6BPnYom.js— another copy of the WhatsApp stack
Each chunk has its own const listeners = new Map(). When setActiveWebListener() is called during connection setup, it populates the Map in whichever chunk's copy of the function is executing. But requireActiveWebListener() (called by the message tool's send path) may resolve to a different chunk's Map that was never populated — or that went stale after a reconnect.
Two different send paths
-
Auto-reply (
deliverWebReply): Usesmsg.reply()on the inbound message object, which holds a direct socket reference. Bypasses the listener Map entirely. Always works. -
Message tool (
sendMessageWhatsApp→requireActiveWebListener): Looks up the listener from the module-scoped Map. Fails when the Map in its chunk is empty or stale.
Why it's intermittent
After a fresh gateway start, all chunk-local Maps may get populated (both monitorWebChannel copies run). But after a WhatsApp reconnect (connection drop + auto-reconnect), only one chunk's Map gets the new listener reference. The auto-reply path keeps working via msg.reply(), but the message tool's chunk has a stale/empty Map.
Evidence
From gateway logs (openclaw-2026-02-11.log):
5:39 PM PT — User says "I'm talking to you on WA right now, why are you detecting disconnects"
- Inbound message received and auto-reply sent successfully (via
extensionAPI.js:46079 deliverWebReply) - Simultaneously, message tool fails:
[tools] message failed: Error: No active WhatsApp Web listener
Heartbeat running from extensionAPI.js:46780 shows connection alive (reconnectAttempts: 1, messagesHandled: 4)
Successful proactive sends (earlier in the day) go through reply-B_4pVbIX.js:9436 sendMessageWhatsApp — confirming that when both Maps are populated, sends work.
Suggested Fix
The listeners Map in src/web/active-listener.ts should be a singleton that survives code-splitting — for example:
- Move it to a shared chunk that all other chunks import (ensure Rollup doesn't duplicate it)
- Or attach it to a global/process-level registry (e.g.,
globalThis.__openclaw_wa_listeners) - Or use a separate tiny module with
sideEffects: trueto prevent tree-shaking duplication
Environment
- OpenClaw version: 2026.2.6-3
- Node: v22.22.0
- OS: macOS (arm64)
- Channel: WhatsApp Web (multi-device)
Workaround
Hard-restart the gateway process (not SIGUSR1 soft restart) to repopulate all Maps. This is fragile and doesn't survive the next WhatsApp reconnect cycle.