{"record":{"id":"f1d2c3bc22a5ec3d","repo":"RocketChat/Rocket.Chat","slug":"error-invalid-contact","errorCode":null,"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/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/ee/server/patches/mergeContacts.ts#L2-L38","documentation":"Thrown by the mergeContacts patch (runMergeContacts) when LivechatContacts.findOneEnabledById(contactId, { session }) returns null — the supplied contactId does not map to an enabled contact record. Because mergeContacts is a transactional operation that needs a primary contact to merge others into, a missing source is fatal. It is raised inside a ClientSession so it participates in transaction rollback.","triggerScenarios":"Calling mergeContacts (or the upstream verifyContactChannel flow that triggers it) with a contactId that was deleted, disabled (enabled:false), or never existed; concurrent merge that already deleted this contact.","commonSituations":"Contact was merged/deleted by another request between lookup and merge; visitor/contact association was cleared; the contactId was extracted from a stale room record; test fixture did not insert the contact.","solutions":["Re-fetch the contact by id and confirm enabled === true before invoking mergeContacts.","If the contact no longer exists, abort the merge flow and surface a user-facing 'contact not found' state.","Make sure the contactId passed is the primary (target) contact, not a similar/secondary one.","Run the merge inside the same session that loaded the contact to avoid TOCTOU deletion."],"exampleFix":"// before\nawait mergeContacts(maybeDeletedId, visitor, session);\n\n// after\nconst contact = await LivechatContacts.findOneEnabledById(contactId, { session });\nif (!contact) return null;\nawait mergeContacts(contactId, visitor, session);","handlingStrategy":"validation","validationCode":"async function contactEnabled(contactId: string, session?: ClientSession): Promise<boolean> {\n  const c = await LivechatContacts.findOneEnabledById(contactId, { session });\n  return Boolean(c);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await mergeContacts(contactId, visitor, session);\n} catch (e) {\n  if (e.message === 'error-invalid-contact') {\n    // contact was deleted/disabled — abort merge and notify\n  } else throw e;\n}","preventionTips":["Load the contact inside the same session you merge in to avoid TOCTOU deletion.","Surface 'contact not found' to the agent UI when upstream lookups fail.","Log contactId lineage so concurrent merges are traceable."],"tags":["omnichannel","contacts","merge","not-found","transactions"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}