[Bug]: WhatsApp outbound 'composing' presence silences phone notifications after the agent replies (selfChatMode 'unavailable' is only sent on connect)
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)
Connect: global presence depends on the unrelated
selfChatModeflag and is sent once.extensions/whatsapp/src/inbound/socket-session.ts:139const presence = options.selfChatMode ? "unavailable" : "available";(
session.ts:345already setsmarkOnlineOnConnect: false, so this explicit call is the only thing that sets the presence.)Every outbound send (text or media, any non-newsletter JID) sends
composingfirst, with no condition:extensions/whatsapp/src/send.ts:287if (!isWhatsAppNewsletterJid(jid)) { await active.assertSendReady?.(to); try { await active.sendComposingTo(to); } catch (err) { /* warn, continue */ } }send.ts:390(sendTypingWhatsApp) does the same.extensions/whatsapp/src/inbound/send-api.ts:299await params.sock.sendPresenceUpdate("composing", jid);Nothing in the plugin sends
paused, andunavailableappears only at connect. In Baileys 7.0.0-rc14 (lib/Socket/chats.js),composingbecomes achatstatenode sent from the companion device, and onlyavailable/unavailablechange the global presence.
Steps to reproduce
- Link WhatsApp as a companion device. Config:
Route the channel to an agent withchannels: { whatsapp: { dmPolicy: "disabled", groupPolicy: "allowlist", groupAllowFrom: ["*"], groups: { "<group-jid>@g.us": { requireMention: true } }, sendReadReceipts: false, selfChatMode: true } }typingMode: "never". - Restart the gateway. From a second account, send a normal DM to the linked number. The phone rings as expected.
- In the group, mention the bot so the agent replies. The reply goes out through
send.tsand acomposingchatstate is sent first. - 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
composingwithpausedonce the send finishes, and re-send the globalunavailablewhen 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 toselfChatMode.
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.
Source: openclaw/openclaw