Skip to content

fix(chatwoot-adapter): stop a migrated @lid contact's inbound from splitting its conversation#31

Merged
rmyndharis merged 1 commit into
mainfrom
feat/chatwoot-inbound-lid-dedup
Jul 3, 2026
Merged

fix(chatwoot-adapter): stop a migrated @lid contact's inbound from splitting its conversation#31
rmyndharis merged 1 commit into
mainfrom
feat/chatwoot-inbound-lid-dedup

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

Summary

Stops a contact who has migrated to WhatsApp's @lid addressing from splitting into a duplicate Chatwoot conversation on the inbound path. This is the inbound mirror of the outbound fix shipped in 0.4.0 (#615).

Problem

resolveConversation looked up the mapping by the raw msg.chatId and created a new conversation on a miss. A contact whose conversation was mapped under <phone>@c.us (pre-migration) and has since migrated arrives with chatId = <lid>@lid, misses the @c.us mapping, and gets a second, duplicate conversation — the thread splits.

Fix

Reuse the host ctx.engine.canonicalChatId resolver (@lid → <phone>@c.us, best-effort) for a dual lookup: getByChat(raw) then getByChat(canonical). A migrated contact's inbound then lands in its existing @c.us conversation instead of a duplicate; a genuinely new chat still creates once.

Deliberate design points (arrived at via review):

  • The per-chat lock stays keyed on the raw chatId, not the canonical id. canonicalChatId is best-effort and non-deterministic (it returns @c.us when the lid→phone cache is warm, @lid when cold, and needs a live engine), so keying the lock on it would let two inbound for the same chat lock different keys and double-create. The raw id is deterministic and already converges a migrated contact's inbound. Canonicalization is used only for the lookup.
  • The lookup's canonicalChatId call is wrapped with a raw fallback, so it can never throw and drop a message before it is de-duplicated/queued (the retry drain re-relays messages whose WA session may be offline).
  • refreshContactName now patches under the key the mapping actually lives under, so reusing a @c.us mapping via @lid records the synced name (no repeated updateContact on every later inbound).

Cold-store residual

canonicalChatId resolves only when the lid→phone mapping is known — after any outbound reply to the contact, or on every inbound when OpenWA's RESOLVE_LID_TO_PHONE=true is set (documented; it also helps the outbound path). A receive-only migrated contact on a default deployment can still create an @lid conversation until the store warms — same behavior as before the fix, now closable with that flag.

Compatibility

Version 0.5.1 (patch, bugfix). No core change (minOpenWAVersion stays 0.8.7). Non-@lid traffic (@c.us, groups) is byte-for-byte unchanged.

…litting its conversation

A contact whose Chatwoot conversation was mapped under <phone>@c.us and has
since migrated to @lid arrives with chatId=@lid, misses the @c.us mapping in
resolveConversation, and gets a duplicate conversation (thread split). The
inbound mirror of the outbound fix in 0.4.0 (#615).

- resolveConversation dual-looks-up: getByChat(raw) then getByChat(canonical)
  (via ctx.engine.canonicalChatId, best-effort), reusing the existing @c.us
  conversation instead of creating a duplicate.
- The per-chat lock stays keyed on the RAW chatId, not the canonical one:
  canonicalChatId is non-deterministic (warm=@c.us / cold=@lid / needs a live
  engine), so a canonical lock key wouldn't reliably serialize two inbound and
  could double-create. The raw id is deterministic and already converges a
  migrated contact's inbound; canonicalization is used only for the lookup, and
  its call is raw-fallback-wrapped so it never throws and drops a message.
- refreshContactName patches under the key the mapping lives under, so reusing a
  @c.us mapping via @lid records the name (no repeated updateContact).

Best-effort: resolves after any reply to the contact, or on every inbound with
OpenWA RESOLVE_LID_TO_PHONE=true (documented). chatwoot-adapter 0.5.1; no core
change.
@rmyndharis rmyndharis merged commit 5d29fef into main Jul 3, 2026
1 check passed
@rmyndharis rmyndharis deleted the feat/chatwoot-inbound-lid-dedup branch July 3, 2026 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant