fix: stop inbox creating duplicate chats for one contact (#363)#364
Merged
Conversation
An inbound message could create a second conversation for a contact under a race (Meta retries a delivery, or a batch fans out to concurrent after() runs). There was no UNIQUE index and no unique-violation backstop on conversations, unlike contacts (migration 022). Worse, once two conversations existed the `.single()` lookup errored on every later message and the webhook created yet another conversation each time, snowballing into a wall of duplicate chats. - findOrCreateConversation now resolves oldest-first with .limit(1) instead of .single(), so pre-existing duplicates converge, and catches 23505 on insert to re-resolve the winning row. - Same hardening applied to the public-API resolver (resolve-conversation). - Migration 036 merges existing duplicate conversations into the oldest thread (re-pointing messages, reactions, deals, flow_runs, notifications, ai_usage_log — no data loss) then adds a UNIQUE (account_id, contact_id) index as the authoritative guarantee. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Updates to Preview Branch (fix/duplicate-conversations) ↗︎
Tasks are run on every commit but only new migration files are pushed.
View logs for this Workflow Run ↗︎. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #363.
Problem
Inbound WhatsApp messages fragmented into many duplicate chats for a single number (see the reporter's screenshot).
Root cause:
findOrCreateConversationin the webhook used.single()to look up the existing conversation..single()errors on both 0 rows and ≥2 rows, and the old code treated any error as "none found" and inserted a new row.after()runs; two inserts both miss the lookup. Unlike contacts (migration 022),conversationshad noUNIQUEindex and no unique-violation backstop..single()errored on every subsequent inbound message, so the webhook created yet another conversation each time — a wall of duplicate chats.The public-API resolver (
resolve-conversation.ts) had the same latent bug via.maybeSingle()(also errors on ≥2 rows).Fix
Mirrors the existing contact-dedup pattern (migration 022):
supabase/migrations/036_conversation_contact_dedup.sql— merges existing duplicate conversations into the oldest thread (re-pointsmessages,message_reactions,deals,flow_runs,notifications,ai_usage_log; rolls up unread counts + last-message summary — no data loss), then addsUNIQUE (account_id, contact_id). This satisfies the reporter's "old users must be merged into a single chat" ask.src/app/api/whatsapp/webhook/route.ts) — lookup now orders oldest-first with.limit(1)(converges on any duplicates instead of erroring), and the insert catches23505to re-resolve the winning row.src/lib/whatsapp/resolve-conversation.ts) — same hardening.Important
Migration required. Applying
036on the production Supabase project is what merges the existing duplicate chats the user is seeing. The code change alone only prevents new duplicates.Testing
tsc --noEmitclean.Out of scope
The issue's secondary line — "Chat Media need to downloaded in local" — is a vague feature request, not the reported bug. Media already works via the
/api/whatsapp/media/:idproxy +chat_mediatable (migration 023). Happy to scope a separate change if local/object-storage persistence is wanted.🤖 Generated with Claude Code