Skip to content

Expose channels.whatsapp.staleEventThresholdMs config key for health monitor #51614

Description

@comicsansbestfont

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:

  1. Gateway restarts (SIGUSR1, update, config patch) → new connection with lastEventAt = now
  2. No inbound message within 30 minutes → health monitor declares stale-socket
  3. closeListener() clears the listeners Map → proactive sends via the message tool fail ("No active WhatsApp Web listener")
  4. Health monitor restarts → new connection → same problem → death spiral
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions