RocketChat/Rocket.Chat · warning · Error
error-contact-has-no-conflicts
error-contact-has-no-conflicts
Error message
error-contact-has-no-conflicts
What it means
resolveContactConflicts refuses to run when the contact exists but contact.conflictingFields is empty or unset. Conflict resolution only makes sense when the contact actually has conflicting field values recorded (they are created by updateContactsCustomFields when overwrite is false). Calling resolve on a clean contact is a state error, not a data corruption.
Source
Thrown at apps/meteor/server/lib/omnichannel/contacts/resolveContactConflicts.ts:31
wipeConflicts?: boolean;
};
export async function resolveContactConflicts(params: ResolveContactConflictsParams): Promise<ILivechatContact> {
const { contactId, name, customFields, contactManager, wipeConflicts } = params;
const contact = await LivechatContacts.findOneEnabledById<Pick<ILivechatContact, '_id' | 'customFields' | 'conflictingFields'>>(
contactId,
{
projection: { _id: 1, customFields: 1, conflictingFields: 1 },
},
);
if (!contact) {
throw new Error('error-contact-not-found');
}
if (!contact.conflictingFields?.length) {
throw new Error('error-contact-has-no-conflicts');
}
if (contactManager) {
await validateContactManager(contactManager);
}
let updatedConflictingFieldsArr: ILivechatContactConflictingField[] = [];
if (wipeConflicts) {
const value = await Settings.incrementValueById('Resolved_Conflicts_Count', contact.conflictingFields.length, {
returnDocument: 'after',
});
if (value) {
void notifyOnSettingChanged(value);
}
} else {
const fieldsToRemove = new Set<string>(
[
name && 'name',View on GitHub (pinned to b2c16d5842)
Solutions
- GET the contact and check conflictingFields is non-empty before showing/calling resolve
- If conflicts were already resolved, treat this as success (idempotent update) rather than retrying
- Disable the resolve action in the UI while a resolve request is in flight
Defensive patterns
Strategy: validation
Validate before calling
const contact = await LivechatContacts.findOneEnabledById(contactId, { projection: { conflictingFields: 1 } });
if (!contact?.conflictingFields?.length) {
// nothing to resolve; treat as no-op success
return contact;
}
await resolveContactConflicts(params); Type guard
const hasConflicts = (c: { conflictingFields?: unknown[] } | null): c is { conflictingFields: NonEmptyArray } =>
Array.isArray(c?.conflictingFields) && (c.conflictingFields as unknown[]).length > 0; Try / catch
try {
await resolveContactConflicts(params);
} catch (err) {
if (err instanceof Error && err.message === 'error-contact-has-no-conflicts') {
// already resolved elsewhere: refresh UI and continue, do not retry
}
throw err;
} Prevention
- Show the resolve action only when conflictingFields is non-empty in the fetched contact
- Disable the resolve button while a request is in flight (prevents double-submit)
- Treat 'no conflicts' as idempotent success in background jobs
When it happens
Trigger: POST /api/v1/omnichannel/contact.resolveConflicts against a contact whose conflictingFields array is empty — e.g. conflicts were already resolved/wiped by a previous call, or the contact never had conflicting writes.
Common situations: Double-submitting the resolve form; a background job and a user both resolving at once; UI not refreshing after a successful resolve so the button is pressed again.
Related errors
- error-contact-not-found
- error-contact-not-found
- error-visitor-not-found
- error-room-already-closed
- error-room-closed
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/f137fcd780303c91.
Report an issue: GitHub.