fix: Prevent duplicate conversations in conversation list (#13713)

Agents using API channel inboxes (e.g., WhatsApp Automate) reported
seeing the same conversation appear twice in their conversation list —
one showing the last message preview and the other showing "No
Messages". Backend investigation confirmed no duplicate conversations
exist in the database, making this purely a frontend issue.

The root cause is a race condition in WebSocket event delivery. When a
conversation is created via the API with auto-assignment, the backend
enqueues multiple ActionCable broadcast jobs (`conversation.created`,
`assignee.changed`, `team.changed`) within milliseconds of each other.
In production with multi-threaded Sidekiq workers, these events can
arrive at the frontend out of order. If `assignee.changed` arrives
before `conversation.created`, the `UPDATE_CONVERSATION` mutation pushes
the conversation into the store (since it doesn't exist yet), and then
`ADD_CONVERSATION` blindly pushes it again — resulting in a duplicate
entry.

The fix adds a uniqueness check in the `ADD_CONVERSATION` mutation to
skip the push if a conversation with the same ID already exists in the
store, matching the dedup pattern already used by
`SET_ALL_CONVERSATION`.
This commit is contained in:
Muhsin Keloth
2026-03-06 14:07:02 +04:00
committed by GitHub
parent 88587b1ccb
commit 939471cb3b
2 changed files with 14 additions and 1 deletions

View File

@@ -228,7 +228,10 @@ export const mutations = {
},
[types.ADD_CONVERSATION](_state, conversation) {
_state.allConversations.push(conversation);
const exists = _state.allConversations.some(c => c.id === conversation.id);
if (!exists) {
_state.allConversations.push(conversation);
}
},
[types.DELETE_CONVERSATION](_state, conversationId) {

View File

@@ -975,6 +975,16 @@ describe('#mutations', () => {
mutations[types.ADD_CONVERSATION](state, conversation);
expect(state.allConversations).toEqual([conversation]);
});
it('should not add a duplicate conversation', () => {
const conversation = { id: 1, messages: [] };
const state = {
allConversations: [conversation],
};
mutations[types.ADD_CONVERSATION](state, { id: 1, messages: [] });
expect(state.allConversations).toHaveLength(1);
});
});
describe('#DELETE_CONVERSATION', () => {