fix(whatsapp): deliver replies to @lid chats via PN JID rewrite#1116
fix(whatsapp): deliver replies to @lid chats via PN JID rewrite#1116juanlotito wants to merge 1 commit into
Conversation
Greptile SummaryFixes silent delivery failures for WhatsApp chats keyed by
Confidence Score: 4/5Safe to merge; the change is narrow and well-contained, with the only realistic failure mode being the pre-existing silent drop that already occurred before this fix. The fix correctly rewrites @lid JIDs across all three outbound paths and falls back gracefully when no mapping is known. The one thing worth watching is that the fallback path — where no PN mapping exists — logs at debug level, so a message silently failing to deliver would be invisible in production logs unless debug logging is enabled. That is a diagnostics gap rather than a regression, but it could make future incidents harder to triage. crates/whatsapp/src/outbound.rs — specifically the None branch of to_deliverable_jid around line 57.
|
| Filename | Overview |
|---|---|
| crates/whatsapp/src/outbound.rs | Adds LID→PN JID rewriting via to_deliverable_jid applied to all three outbound paths (text, media, typing). Logic is correct and well-tested; one minor concern about debug! level for the silent-drop fallback. |
Sequence Diagram
sequenceDiagram
participant App as moltis App
participant Out as WhatsAppOutbound
participant Fn as to_deliverable_jid
participant Lib as whatsapp_rust Client
participant WA as WhatsApp Server
App->>Out: "send_text(to=id@lid, text)"
Out->>Out: "resolve_jid to Jid(@lid)"
Out->>Fn: "to_deliverable_jid(client, jid@lid)"
Fn->>Fn: "needs_lid_resolution? true"
Fn->>Lib: "get_phone_number_from_lid(id)"
alt mapping found
Lib-->>Fn: "Some(pn)"
Fn-->>Out: "Jid(@s.whatsapp.net)"
Out->>Lib: "send_message(pn@s.whatsapp.net, msg)"
Lib->>WA: "stanza to @s.whatsapp.net"
WA-->>Lib: "Delivered receipt"
else no mapping
Lib-->>Fn: "None"
Fn-->>Out: "Jid(@lid) unchanged"
Out->>Lib: "send_message(id@lid, msg)"
Lib->>WA: "stanza to @lid"
WA-->>WA: "dropped silently"
end
Reviews (1): Last reviewed commit: "fix(whatsapp): deliver replies to @lid c..." | Re-trigger Greptile
Review feedback (PR moltis-org#1116): the no-mapping fallback still sends to the `@lid` JID, which is exactly the condition the WhatsApp server drops without a `Delivered` receipt. Logging it at `debug!` left undelivered replies invisible in production unless debug logging was enabled. Raise it to `warn!` so the event surfaces by default; this is an unexpected but recoverable state, matching the project's logging-level guidance.
Replies addressed to a privacy-enabled sender's `@lid` JID were accepted by the WhatsApp server but silently dropped: no `Delivered` receipt ever arrived, so the agent appeared to respond (the reply was visible in the web UI) while the user received nothing on WhatsApp. Sending the same content to the sender's phone-number JID (`@s.whatsapp.net`) delivered normally. Root cause: inbound chats from senders with number-privacy enabled are keyed by LID, and the bundled whatsapp-rust 0.5 does not auto-resolve a `@lid` destination to its phone number before sending (LID/PN address unification landed upstream in 0.6). Fix: before each outbound send (text, media, typing) resolve a `@lid` destination to its PN JID through the existing `Client::get_phone_number_from_lid` mapping. When no mapping is known the JID is left unchanged so behaviour matches the previous path; this fallback logs at `warn!` so an undelivered reply is visible without enabling debug logging. The `@lid` check uses the `HIDDEN_USER_SERVER` constant rather than a literal. Adds unit coverage for the pure `needs_lid_resolution` decision (lid rewrites, pn/group/bare-number pass through).
d0a6fb0 to
47ee49a
Compare
Live testing on 2026-07-01 showed that even with whatsapp-rust 0.6's unified LID/PN addressing, stanzas sent from a companion device to a bare @lid chat are accepted by the server but never delivered (no Delivered receipt), while the same message to the mapped PN JID arrives normally. Inbound LID handling from 0.6 works correctly and is unaffected. Restore the to_deliverable_jid rewrite (originally PR moltis-org#1116) using the 0.6 API: client.get_lid_pn_entry() replaces the removed get_phone_number_from_lid(). Lookup failures and missing mappings fall back to sending to the @lid as-is, with a warn so undelivered replies are visible at default log level.
Summary
Replies sent to a privacy-enabled sender's @lid chat were silently dropped on WhatsApp. The
gateway ran the agent, produced a reply (visible in the web UI), and called the outbound
sender — but the message never reached the user and no Delivered receipt ever came back.
Sending the same text to the sender's phone-number JID (@s.whatsapp.net) delivered fine.
Root cause: inbound chats from senders who have number-privacy enabled are keyed by LID
(@lid). The bundled whatsapp-rust 0.5 does not auto-resolve a @lid destination to its
phone number before sending (LID/PN address unification landed upstream in 0.6). The
WhatsApp server accepts the stanza addressed to @lid but drops it.
Fix: before each outbound send (text, media, typing), resolve a @lid destination to its PN
JID via the existing Client::get_phone_number_from_lid mapping (already populated by the
library). When no mapping is known the JID is left unchanged, so the behaviour matches the
previous path. The @lid check uses the HIDDEN_USER_SERVER constant instead of a string
literal.
The change is isolated to crates/whatsapp/src/outbound.rs (+65/-4).
Validation
Completed (locally, pinned nightly nightly-2025-12-27)
unit tests for needs_lid_resolution)
Remaining (left to CI)
Manual QA
Reproduced and verified end-to-end on a live self-hosted gateway (ZimaBoard / Docker) paired
to a real WhatsApp number, with the sender's number-privacy enabled so the chat is keyed by
@lid.
the send "succeed" with no Delivered receipt. A diagnostic send_message to the same person's
classic @s.whatsapp.net JID did arrive — confirming transport works and only @lid
addressing was broken.
DEBUG moltis_whatsapp::outbound: rewriting LID destination to PN JID lid=@lid
pn=
DEBUG whatsapp_rust::receipt: Received receipt type 'Delivered' for message
Notes for maintainers
0.6, which brings unified LID/PN addressing; that is a larger change with breakage risk, so
this PR keeps the surface minimal. Happy to follow up on the bump if preferred.
existing constant (HIDDEN_USER_SERVER).