#150943·OpenClaw

[Bug]: WhatsApp outbound 'composing' presence silences phone notifications after the agent replies (selfChatMode 'unavailable' is only sent on connect)

Author: husodrn46Created Sep 17, 2026Updated Sep 17, 2026
LabelsP2issue-rating: 🦪 silver shellfishimpact:ux-friction

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

With the WhatsApp channel linked as a companion device, every outbound message is preceded by a composing chatstate, and nothing ever sends paused or a global unavailable afterwards. After the agent replies, the phone stops ringing for incoming messages: the unread badge still goes up, but there is no sound and no banner. Messages are not marked as read (sendReadReceipts: false), so this is not the read-receipt problem. Notifications come back after the next socket reconnect, and stop again after the next reply.

channels.whatsapp.selfChatMode: true is currently the only way to get a global unavailable presence, and it is only sent once, at connect time. Nothing re-asserts it after the plugin has shown activity through composing.

The agent-level typingMode: "never" does not help here. It only controls the inbound typing indicator (inbound/message-delivery.ts), not the pre-send composing in send.ts.

Related, both closed as not planned: #4500 (stealth mode to preserve phone notifications) and #78314 (no typing animation on mobile). Those two pull in opposite directions, which is why I think this should be configurable rather than removed.

Code path (main @ 98a649d651f)

  1. Connect: global presence depends on the unrelated selfChatMode flag and is sent once. extensions/whatsapp/src/inbound/socket-session.ts:139

    const presence = options.selfChatMode ? "unavailable" : "available";
    

    (session.ts:345 already sets markOnlineOnConnect: false, so this explicit call is the only thing that sets the presence.)

  2. Every outbound send (text or media, any non-newsletter JID) sends composing first, with no condition: extensions/whatsapp/src/send.ts:287

    if (!isWhatsAppNewsletterJid(jid)) {
      await active.assertSendReady?.(to);
      try {
        await active.sendComposingTo(to);
      } catch (err) { /* warn, continue */ }
    }
    

    send.ts:390 (sendTypingWhatsApp) does the same.

  3. extensions/whatsapp/src/inbound/send-api.ts:299

    await params.sock.sendPresenceUpdate("composing", jid);
    
  4. Nothing in the plugin sends paused, and unavailable appears only at connect. In Baileys 7.0.0-rc14 (lib/Socket/chats.js), composing becomes a chatstate node sent from the companion device, and only available/unavailable change the global presence.

Steps to reproduce

  1. Link WhatsApp as a companion device. Config:
    channels: { whatsapp: {
      dmPolicy: "disabled",
      groupPolicy: "allowlist",
      groupAllowFrom: ["*"],
      groups: { "<group-jid>@g.us": { requireMention: true } },
      sendReadReceipts: false,
      selfChatMode: true
    } }
    
    Route the channel to an agent with typingMode: "never".
  2. Restart the gateway. From a second account, send a normal DM to the linked number. The phone rings as expected.
  3. In the group, mention the bot so the agent replies. The reply goes out through send.ts and a composing chatstate is sent first.
  4. From the second account, send another DM to the linked number.

Expected behavior

With selfChatMode: true (or whatever setting ends up controlling this), the linked gateway should not keep the account looking "active", and the phone should keep notifying normally after the bot has replied. Ideally:

  • follow composing with paused once the send finishes, and re-send the global unavailable when presence is meant to be unavailable (debounced, so each chunk doesn't trigger it), and/or
  • add explicit config such as channels.whatsapp.presence: "available" | "unavailable" and a switch for the outbound pre-send typing indicator, instead of tying presence to selfChatMode.

Actual behavior

After step 3, the phone shows only the unread badge for new messages: no sound, no banner, and the messages stay unread. This lasts until the socket reconnects, which sends unavailable again. The gateway reconnects on its own several times a day (watchdog app-silent restarts, 408/428/499/503 closes), so notifications come and go at seemingly random times.

Before selfChatMode was enabled (connect sent a global available), the phone got no notifications at all while the gateway was connected. Enabling it plus a restart fixed that, which is how the regression after replies became visible.

Caveat: the link between the composing chatstate and the silenced notifications is inferred from the code path above and the pattern I saw (silent after a bot reply, back after a reconnect). I did not isolate it in a controlled experiment. A local patch that sends unavailable about 8 s after composing would test it directly, but I have not applied it.

OpenClaw version

2026.9.4 (CLI, gateway, macOS app). @openclaw/whatsapp 2026.9.4 (Baileys 7.0.0-rc14)

Operating system

macOS 27.0 (Apple silicon)

Install method

npm global CLI. Gateway runs as a LaunchAgent (loopback), plus the macOS app.

Model

openai/gpt-5.6-luna (not model-related)

Provider / routing chain

N/A. This is transport/presence behavior in the WhatsApp channel plugin.

Impact and severity

Medium. Anyone who links their personal WhatsApp account and lets an agent reply in a group quietly loses phone notifications for all their chats. It is hard to diagnose because nothing is marked read and the badge still updates.