{"record":{"id":"efdfa320b176a774","repo":"RocketChat/Rocket.Chat","slug":"error-contact-not-found-efdfa3","errorCode":"error-contact-not-found","errorMessage":"error-contact-not-found","messagePattern":"error-contact-not-found","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/lib/omnichannel/contacts/disableContact.ts","lineNumber":10,"sourceCode":"import type { ILivechatContact } from '@rocket.chat/core-typings';\nimport { LivechatContacts, LivechatRooms } from '@rocket.chat/models';\n\nimport { settings } from '../../../settings';\nimport { removeGuest } from '../guests';\n\nexport async function disableContactById(contactId: string): Promise<void> {\n\tconst contact = await LivechatContacts.findOneEnabledById<Pick<ILivechatContact, '_id' | 'channels'>>(contactId);\n\tif (!contact) {\n\t\tthrow new Error('error-contact-not-found');\n\t}\n\n\t// Checking if the contact has any open channel/room before removing its data.\n\tconst contactOpenRooms = await LivechatRooms.checkContactOpenRooms(contactId);\n\tif (contactOpenRooms && !settings.get<boolean>('Livechat_Allow_collect_and_store_HTTP_header_informations')) {\n\t\tthrow new Error('error-contact-has-open-rooms');\n\t}\n\n\t// Cleaning contact/visitor data;\n\tawait Promise.all(contact.channels.map((channel) => removeGuest({ _id: channel.visitor.visitorId })));\n\n\tawait LivechatContacts.disableByContactId(contactId);\n}\n","sourceCodeStart":1,"sourceCodeEnd":24,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/lib/omnichannel/contacts/disableContact.ts#L1-L24","documentation":"disableContactById looks the contact up with findOneEnabledById and throws Error('error-contact-not-found') when nothing is returned - meaning the id matches no contact OR matches a contact that is already disabled. All downstream cleanup (guest removal across channels, contact disable) is skipped when this throws.","triggerScenarios":"Disabling a contact twice (double submit or retry after timeout); a sync job re-processing contacts that were already disabled; passing a wrong or foreign contact id.","commonSituations":"Retry logic without idempotency awareness in integrations; nightly syncs that do not track disabled state; environment drift of contact ids.","solutions":["Check for an enabled contact first and treat 'already disabled' as success in retry flows","Resolve the correct contact id via search before disabling","Debounce double submissions in the UI/API client"],"exampleFix":"// before - throws on the second call\nawait disableContactById(contactId);\n\n// after - idempotent disable\nconst enabled = await LivechatContacts.findOneEnabledById(contactId, { projection: { _id: 1 } });\nif (enabled) {\n  await disableContactById(contactId);\n}","handlingStrategy":"validation","validationCode":"import { LivechatContacts } from '@rocket.chat/models';\n\n// idempotent disable: only act when an enabled contact exists\nconst enabled = await LivechatContacts.findOneEnabledById(contactId, { projection: { _id: 1 } });\nif (enabled) {\n  await disableContactById(contactId);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await disableContactById(contactId);\n} catch (err: any) {\n  if (err?.message === 'error-contact-not-found') return; // already disabled or never existed\n  throw err;\n}","preventionTips":["Make disable flows idempotent: check enabled state before disabling","Debounce double-submits on contact management UIs","Sync jobs should skip contacts that are already disabled"],"tags":["omnichannel","contacts","not-found","idempotency"],"backgroundTag":"contact-not-found","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}