-
-
Notifications
You must be signed in to change notification settings - Fork 69.4k
WhatsApp active-listener singleton not shared across bundled chunks (outbound sends fail) #45994
Description
Bug Description
Proactive WhatsApp message sending (via CLI message send, cron announce delivery, or agent message tool) fails with:
Error: No active WhatsApp Web listener (account: default). Start the gateway, then link WhatsApp with: openclaw channels login --channel whatsapp --account default.
Despite WhatsApp being fully connected — channels status --probe shows linked, running, connected, and inbound auto-replies work correctly.
Root Cause
src/web/active-listener.ts defines a module-scope listeners = new Map() singleton. The bundler (tsdown) emits this module into two separate chunks:
model-selection-*.js(used by the gateway WebSocket handler / agent outbound path)reply-*.js(used bychannel-web-*.jswhich initializes the WhatsApp connection)
Each chunk gets its own independent listeners Map. When channel-web calls setActiveWebListener(), it registers the listener in the reply-*.js Map. When the gateway WebSocket handler calls requireActiveWebListener() to send a proactive message, it checks the model-selection-*.js Map — which is always empty.
This is the exact same class of bug fixed by PR #43683 ("Runtime: share singleton state across bundled chunks") for Telegram, Slack, Signal, iMessage, and core pipeline singletons. WhatsApp's active-listener.ts was not included in that fix.
Steps to Reproduce
- Start gateway with WhatsApp linked
- Verify
openclaw channels status --probeshowsconnected - Send an inbound WhatsApp message → auto-reply works ✅
- Run
openclaw message send --channel whatsapp --target "+1234567890" --message "test"→ fails with "No active WhatsApp Web listener" ❌ - Run any cron with WhatsApp delivery → same failure ❌
Expected Behavior
Proactive sends should work when WhatsApp is connected.
Workaround
Patch both chunks to share the Map via globalThis:
// In both model-selection-*.js and reply-*.js, replace:
const listeners = /* @__PURE__ */ new Map();
// With:
const listeners = globalThis.__openclaw_wa_listeners ??= /* @__PURE__ */ new Map();Proposed Fix
Apply the same resolveGlobalSingleton(Symbol.for("openclaw.wa-listeners")) pattern from PR #43683 to src/web/active-listener.ts.
Environment
- OpenClaw: 2026.3.13 (also confirmed on 2026.3.12)
- Node: 22.22.0
- Platform: Linux (Debian)
Related
- PR fix(runtime): duplicate messages, share singleton state across bundled chunks #43683 (same bug class, fixed for other channels but not WhatsApp)
- Issue Announce queue delivery fails with 'No active WhatsApp Web listener' despite WhatsApp being connected #30177 (same symptom, different root cause attribution)