{"record":{"id":"f137fcd780303c91","repo":"RocketChat/Rocket.Chat","slug":"error-contact-has-no-conflicts","errorCode":"error-contact-has-no-conflicts","errorMessage":"error-contact-has-no-conflicts","messagePattern":"error-contact-has-no-conflicts","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"apps/meteor/server/lib/omnichannel/contacts/resolveContactConflicts.ts","lineNumber":31,"sourceCode":"\twipeConflicts?: boolean;\n};\n\nexport async function resolveContactConflicts(params: ResolveContactConflictsParams): Promise<ILivechatContact> {\n\tconst { contactId, name, customFields, contactManager, wipeConflicts } = params;\n\n\tconst contact = await LivechatContacts.findOneEnabledById<Pick<ILivechatContact, '_id' | 'customFields' | 'conflictingFields'>>(\n\t\tcontactId,\n\t\t{\n\t\t\tprojection: { _id: 1, customFields: 1, conflictingFields: 1 },\n\t\t},\n\t);\n\n\tif (!contact) {\n\t\tthrow new Error('error-contact-not-found');\n\t}\n\n\tif (!contact.conflictingFields?.length) {\n\t\tthrow new Error('error-contact-has-no-conflicts');\n\t}\n\n\tif (contactManager) {\n\t\tawait validateContactManager(contactManager);\n\t}\n\n\tlet updatedConflictingFieldsArr: ILivechatContactConflictingField[] = [];\n\tif (wipeConflicts) {\n\t\tconst value = await Settings.incrementValueById('Resolved_Conflicts_Count', contact.conflictingFields.length, {\n\t\t\treturnDocument: 'after',\n\t\t});\n\t\tif (value) {\n\t\t\tvoid notifyOnSettingChanged(value);\n\t\t}\n\t} else {\n\t\tconst fieldsToRemove = new Set<string>(\n\t\t\t[\n\t\t\t\tname && 'name',","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/lib/omnichannel/contacts/resolveContactConflicts.ts#L13-L49","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const contact = await LivechatContacts.findOneEnabledById(contactId, { projection: { conflictingFields: 1 } });\nif (!contact?.conflictingFields?.length) {\n  // nothing to resolve; treat as no-op success\n  return contact;\n}\nawait resolveContactConflicts(params);","typeGuard":"const hasConflicts = (c: { conflictingFields?: unknown[] } | null): c is { conflictingFields: NonEmptyArray } =>\n  Array.isArray(c?.conflictingFields) && (c.conflictingFields as unknown[]).length > 0;","tryCatchPattern":"try {\n  await resolveContactConflicts(params);\n} catch (err) {\n  if (err instanceof Error && err.message === 'error-contact-has-no-conflicts') {\n    // already resolved elsewhere: refresh UI and continue, do not retry\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["omnichannel","livechat","contacts","conflicts","precondition","state"],"backgroundTag":"precondition-failed","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}