{"record":{"id":"f1d2c3bc22a5ec3d","repo":"RocketChat/Rocket.Chat","slug":"error-invalid-contact","errorCode":"error-invalid-contact","errorMessage":"error-invalid-contact","messagePattern":"error-invalid-contact","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/ee/server/patches/mergeContacts.ts","lineNumber":20,"sourceCode":"import { License } from '@rocket.chat/license';\nimport { LivechatContacts, LivechatRooms, Settings } from '@rocket.chat/models';\nimport type { ClientSession } from 'mongodb';\n\nimport { isSameChannel } from '../../../app/livechat/lib/isSameChannel';\nimport { notifyOnSettingChanged } from '../../../server/lib/notifyListener';\nimport { ContactMerger } from '../../../server/lib/omnichannel/contacts/ContactMerger';\nimport { mergeContacts } from '../../../server/lib/omnichannel/contacts/mergeContacts';\nimport { contactLogger as logger } from '../lib/omnichannel/logger';\n\nexport const runMergeContacts = async (\n\t_next: any,\n\tcontactId: string,\n\tvisitor: ILivechatContactVisitorAssociation,\n\tsession?: ClientSession,\n): Promise<ILivechatContact | null> => {\n\tconst originalContact = await LivechatContacts.findOneEnabledById(contactId, { session });\n\tif (!originalContact) {\n\t\tthrow new Error('error-invalid-contact');\n\t}\n\n\tconst channel = originalContact.channels.find((channel: ILivechatContactChannel) => isSameChannel(channel.visitor, visitor));\n\tif (!channel) {\n\t\tthrow new Error('error-invalid-channel');\n\t}\n\n\tlogger.debug({ msg: 'Getting similar contacts', contactId });\n\n\tconst similarContacts: ILivechatContact[] = await LivechatContacts.findSimilarVerifiedContacts(channel, contactId, { session });\n\n\tif (!similarContacts.length) {\n\t\tlogger.debug({ msg: 'No similar contacts found', contactId });\n\t\treturn originalContact;\n\t}\n\n\tlogger.debug({ msg: 'Found contacts to merge', contactId, count: similarContacts.length });\n\tfor await (const similarContact of similarContacts) {","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/ee/server/patches/mergeContacts.ts#L2-L38","documentation":"The enterprise patch runMergeContacts (apps/meteor/ee/server/patches/mergeContacts.ts:20) overrides contact merging: the contact to merge into must exist and be enabled (LivechatContacts.findOneEnabledById). A contactId that is unknown, deleted, or already disabled — merging into another contact disables the source record — throws error-invalid-contact.","triggerScenarios":"Calling mergeContacts(contactId, visitor, session?) where contactId no longer resolves to an enabled contact: the contact was already merged (and thus disabled), deleted, or the id is wrong.","commonSituations":"Retrying a merge whose first attempt already disabled the contact; concurrent merges targeting the same contact; stale contact ids held by channel webhooks or integrations; contacts disabled by a previous merge operation.","solutions":["Verify the contact is still enabled before merging: LivechatContacts.findOneEnabledById(contactId)","If the contact was already merged, retarget the merge at the surviving (enabled) contact","Make merge flows idempotent: treat error-invalid-contact as 'already merged/deleted' success","Inspect the contact registry to find which record absorbed it"],"exampleFix":"// before\nawait mergeContacts(contactId, visitor);\n\n// after\nconst contact = await LivechatContacts.findOneEnabledById(contactId);\nif (!contact) {\n\t// already merged or deleted — find the surviving contact or skip\n\treturn null;\n}\nawait mergeContacts(contactId, visitor);","handlingStrategy":"validation","validationCode":"const contact = await LivechatContacts.findOneEnabledById(contactId);\nif (!contact) {\n\t// contact missing or already merged/disabled — retarget or skip\n\treturn null;\n}\nreturn mergeContacts(contactId, visitor);","typeGuard":"const isMergeableContact = (contact: ILivechatContact | null): contact is ILivechatContact => !!contact && contact.disabled !== true;","tryCatchPattern":"try {\n\treturn await mergeContacts(contactId, visitor);\n} catch (e) {\n\tif (e instanceof Error && e.message === 'error-invalid-contact') {\n\t\t// contact already merged/disabled: find the surviving contact and merge into it instead\n\t}\n\tthrow e;\n}","preventionTips":["Verify the contact is enabled immediately before merging","Make merge flows idempotent: a second merge attempt against the same contact should resolve to the surviving record","In channel/webhook handlers, look contacts up by verified channel rather than persisting contact ids"],"tags":["omnichannel","contacts","merge","not-found","visitor"],"backgroundTag":"resource-not-found","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}