Summary
The WhatsApp channel health monitor declares a socket "stale" and kills the connection if no inbound message is received within 30 minutes. This threshold is hardcoded and cannot be configured, causing a death-loop on accounts with low inbound traffic.
Current Behavior
In src/web/auto-reply/monitor.ts, the stale threshold is:
const MESSAGE_TIMEOUT_MS = tuning.messageTimeoutMs ?? 1800 * 1e3; // 30 minutes
And in src/gateway/channel-health-monitor.ts:
staleEventThresholdMs: deps.timing?.staleEventThresholdMs ?? 18e5 // 30 minutes
The code already reads from a tuning object — but that tuning is never populated from config. There is no channels.whatsapp.* schema key to override it.
The Problem
For WhatsApp accounts that receive only a few messages per day (e.g., a family member, a low-traffic business line), the 30-minute window is too aggressive:
- Gateway restarts (SIGUSR1, update, config patch) → new connection with
lastEventAt = now
- No inbound message within 30 minutes → health monitor declares
stale-socket
closeListener() clears the listeners Map → proactive sends via the message tool fail ("No active WhatsApp Web listener")
- Health monitor restarts → new connection → same problem → death spiral
- Eventually hits status 440 (session conflict) from overlapping connections → permanently dead
Auto-reply (which uses the direct Baileys sock reference) continues to work, but proactive agent-initiated sends break because they resolve the listener from the in-memory Map that was cleared.
Observed today: 16 stale-socket restarts in one day on an account that handled 1 inbound message. Each restart produced a new connection that immediately started the 30-minute countdown again.
Proposed Fix
Expose the stale threshold to config, e.g.:
{
"channels": {
"whatsapp": {
"healthMonitor": {
"staleEventThresholdMs": 14400000
}
}
}
}
The plumbing already exists — tuning.messageTimeoutMs flows through to MESSAGE_TIMEOUT_MS and staleEventThresholdMs flows through deps.timing. It just needs a config schema key and the wiring to pass it through.
Secondary suggestion
Consider also counting outbound messages (agent sends, auto-reply) as "events" for the stale check. Currently only inbound messages update lastEventAt via the onMessage callback. A WhatsApp account that is actively sending but not receiving would still be declared stale.
Environment
- OpenClaw: 2026.3.11
- Platform: macOS 15.7.4 (arm64)
- Node: v25.8.1
- WhatsApp: Baileys Web provider, single account, dmPolicy: "allowlist"
Workaround
Restart the gateway and send a message to the WhatsApp account within 30 minutes to prime lastEventAt. This is fragile and breaks again on the next quiet stretch.
Summary
The WhatsApp channel health monitor declares a socket "stale" and kills the connection if no inbound message is received within 30 minutes. This threshold is hardcoded and cannot be configured, causing a death-loop on accounts with low inbound traffic.
Current Behavior
In
src/web/auto-reply/monitor.ts, the stale threshold is:And in
src/gateway/channel-health-monitor.ts:The code already reads from a
tuningobject — but thattuningis never populated from config. There is nochannels.whatsapp.*schema key to override it.The Problem
For WhatsApp accounts that receive only a few messages per day (e.g., a family member, a low-traffic business line), the 30-minute window is too aggressive:
lastEventAt = nowstale-socketcloseListener()clears thelistenersMap → proactive sends via themessagetool fail ("No active WhatsApp Web listener")Auto-reply (which uses the direct Baileys
sockreference) continues to work, but proactive agent-initiated sends break because they resolve the listener from the in-memory Map that was cleared.Observed today: 16 stale-socket restarts in one day on an account that handled 1 inbound message. Each restart produced a new connection that immediately started the 30-minute countdown again.
Proposed Fix
Expose the stale threshold to config, e.g.:
{ "channels": { "whatsapp": { "healthMonitor": { "staleEventThresholdMs": 14400000 } } } }The plumbing already exists —
tuning.messageTimeoutMsflows through toMESSAGE_TIMEOUT_MSandstaleEventThresholdMsflows throughdeps.timing. It just needs a config schema key and the wiring to pass it through.Secondary suggestion
Consider also counting outbound messages (agent sends, auto-reply) as "events" for the stale check. Currently only inbound messages update
lastEventAtvia theonMessagecallback. A WhatsApp account that is actively sending but not receiving would still be declared stale.Environment
Workaround
Restart the gateway and send a message to the WhatsApp account within 30 minutes to prime
lastEventAt. This is fragile and breaks again on the next quiet stretch.