fix(whatsapp): resolve outbound PN to LID via auth-dir forward mapping#74925
Conversation
|
Codex review: needs maintainer review before merge. Summary Reproducibility: yes. source-level. Current Real behavior proof Next step before merge Security Review detailsBest possible solution: Merge the focused WhatsApp plugin fix after normal maintainer and CI gates, and track the re-link reverse-mapping concern separately if maintainers want that follow-up. Do we have a high-confidence way to reproduce the issue? Yes, source-level. Current Is this the best way to solve the issue? Yes. Resolving phone-number recipients against the same account auth directory inside the active send API is the narrow maintainable fix for message, poll, and composing delivery; the separate reverse-mapping re-link behavior should stay as follow-up work. What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 92284bc46043. |
496cfcb to
d8d3ec5
Compare
483d3d6 to
958cfd1
Compare
openclaw#67378) When an agent proactively sends a WhatsApp message to a phone number whose contact uses LID internally, the outbound JID was always built as `{digits}@s.whatsapp.net`. WhatsApp Web routes that to a sender-only ghost chat that never reaches the recipient — the message appears sent (double check) but is invisible on Android/iOS and on the recipient's device. Baileys writes a per-account forward mapping `lid-mapping-{pnUser}.json` in the auth dir whenever an LID-PN pair is observed (see Signal/lid-mapping.js storeLIDPNMappings). Inbound already consults this directory through readLidReverseMapping for the LID->PN direction; outbound had no symmetric helper. Add `readLidForwardMapping` and `toWhatsappJidWithLid(num, { authDir })` in targets-runtime, mirroring the existing reverse helper. Thread the account's `authDir` from the inbound monitor into `createWebSendApi`, and route sendMessage / sendPoll / sendComposingTo through a new `resolveOutboundJid` that uses the LID JID when a forward mapping exists, falling back to PN-only behavior otherwise. sendReaction's chatJid still uses the existing PN-aware `toWhatsappJid` because callers pass an already-formed JID (group, DM, or LID); the participant key follows the same legacy contract. `outbound-base.ts` and `send.ts` outer entries are unchanged: those `toWhatsappJid` calls only feed redacted log strings; the actual send goes through `active.sendMessage(to, ...)` which now does LID resolution.
958cfd1 to
5f51cb7
Compare
|
Merged via squash.
Thanks @edenfunf! |
openclaw#74925) Merged via squash. Prepared head SHA: 5f51cb7 Co-authored-by: edenfunf <146086744+edenfunf@users.noreply.github.com> Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com> Reviewed-by: @mcaxtr
openclaw#74925) Merged via squash. Prepared head SHA: 5f51cb7 Co-authored-by: edenfunf <146086744+edenfunf@users.noreply.github.com> Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com> Reviewed-by: @mcaxtr
openclaw#74925) Merged via squash. Prepared head SHA: 5f51cb7 Co-authored-by: edenfunf <146086744+edenfunf@users.noreply.github.com> Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com> Reviewed-by: @mcaxtr
Summary
sendMessage/sendPoll/sendComposingTo) now resolve a phone number to{lid}@lidvialid-mapping-{phoneDigits}.jsonin the account auth dir when present, falling back to{digits}@s.whatsapp.netotherwise. New helpersreadLidForwardMapping+toWhatsappJidWithLidare added beside the existingreadLidReverseMapping/jidToE164to keep the LID handling symmetric.outbound-base.ts:131andsend.tsouter entries keeptoWhatsappJidbecause those produce redacted log strings only; the actual delivery flows throughactive.sendMessage(to, ...).sendReaction'schatJidkeepstoWhatsappJidbecause callers pass already-formed JIDs (group / DM / LID). The companionlid-mapping-{lid}_reverse.jsonregeneration after re-link, mentioned at the end of [Bug]: WhatsApp outbound messages create ghost chats due to missing LID→PN resolution in createWebSendApi #67378, is left out as a separate fix.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause
toWhatsappJidinextensions/whatsapp/src/targets-runtime.tsis purely lexical — it maps any phone number to{digits}@s.whatsapp.netwith no awareness of LID. The reverse direction (LID JID → PN) was already wired throughreadLidReverseMappingreadinglid-mapping-{lid}_reverse.json, but the forward direction (PN → LID) was missing entirely on the outbound path. When Baileys' contact uses LID internally, sending tos.whatsapp.netcreates a separate sender-side thread.lid-mappingkeyspace —[pnUser]: lidUserand[lidUser_reverse]: pnUser— but only the reverse half had a code path here.Regression Test Plan
extensions/whatsapp/src/text-runtime.test.ts—describe("toWhatsappJidWithLid (issue #67378)", ...)extensions/whatsapp/src/inbound/send-api.test.ts—describe("createWebSendApi LID resolution (issue #67378)", ...)lid-mapping-{phoneDigits}.jsoncontaining a LID, outboundsendMessage/sendPoll/sendComposingTomust callsock.sendMessage(orsendPresenceUpdate) with{lid}@lid. With no forward mapping, the legacy{digits}@s.whatsapp.netJID must still be used. WithoutauthDir, behavior must be identical to before this PR.createWebSendApi, which is exactly where the new tests assert. The unit test ontoWhatsappJidWithLidpins down the helper contract; the integration test confirms theauthDiris threaded all the way through and that all three outbound entry points use it.createWebSendApitests instantiate withoutauthDir, so they never exercised LID resolution.User-visible / Behavior Changes
Diagram (if applicable)
Security Impact (required)
Repro + Verification
Environment
origin/mainSteps
lid-mapping-*.jsonfor them.messagetool to send to that contact's phone number.Expected
{lid}@lid. Message arrives in the recipient's existing thread on Android/iOS and on the recipient's WhatsApp.Actual (before this PR)
{digits}@s.whatsapp.net. A ghost chat appears only on the sender's WA Web; recipient never sees the message.Evidence
The new
text-runtime.test.tsandsend-api.test.tsLID describe blocks fail againstmainand pass against this branch.Human Verification (required)
toWhatsappJidWithLid) and integration-level (5 cases forcreateWebSendApi) covering: PN with mapping, PN without mapping, numeric LID values, already-formed JIDs (group, s.whatsapp.net, lid), legacy path with noauthDir, andsendPoll/sendComposingToparity withsendMessage.whatsapp:prefix, group JIDs passing through unchanged, missing forward file falling back to PN, no-authDirregression guard.{lid}:N@lid) — Baileys' on-disk format does not store device specificity for the forward direction, so the LID JID emitted is device-less, matching the working pattern the reporter applied to the compiled bundle.@hosted.lidoutbound — same pattern would apply but was not in scope here.Review Conversations
Compatibility / Migration
authDiris opt-in and defaults to legacy PN-only behavior.Risks and Mitigations
storeLIDPNMappingswhenever a fresh pair is observed. No new write path introduced here.withTempDir/beforeEachcreates a freshmkdtempSyncand theafterEachremoves it.