RocketChat/Rocket.Chat · error · Error
error-invalid-channel
error-invalid-channel
Error message
error-invalid-channel
What it means
Thrown by the Enterprise Edition patch of LivechatContacts.mergeContacts (runMergeContacts) when the contact found by contactId is enabled, but none of its channels has a visitor association matching the visitor argument (compared with isSameChannel, i.e. visitorId + source). The merge needs the matching channel to search for similar verified contacts, so it aborts before merging. Sibling error 'error-invalid-contact' covers the case where the contact itself does not exist or is disabled.
Solutions
- Re-resolve the contact for the visitor at call time (e.g. via LivechatContacts find-by-visitor queries) instead of trusting a cached contactId, then retry the merge.
- Before calling, verify the visitor association exists: contact.channels.some((ch) => isSameChannel(ch.visitor, visitor)).
- If the contact was already merged, locate the surviving/target contact and run the merge against that id.
- Check for concurrent verification runs for the same visitor and serialize them (queue or lock) to avoid races.
Example fix
// before
await runMergeContacts(null, contactId, { visitorId, source }, session);
// after
const contact = await LivechatContacts.findOneEnabledById(contactId, { session });
if (!contact) throw new Error('error-invalid-contact');
if (!contact.channels.some((ch) => isSameChannel(ch.visitor, { visitorId, source }))) {
// visitor no longer tied to this contact: re-resolve the current contactId for the visitor
throw new Error('stale contactId: visitor channel not on this contact');
}
await runMergeContacts(null, contactId, { visitorId, source }, session); Defensive patterns
Strategy: validation
Validate before calling
const contact = await LivechatContacts.findOneEnabledById(contactId, { session });
const hasChannel = !!contact?.channels.find((ch: ILivechatContactChannel) => isSameChannel(ch.visitor, visitor));
if (!hasChannel) {
// do not call mergeContacts; re-resolve the contact for this visitor first
} Type guard
import { isSameChannel } from '~/app/livechat/lib/isSameChannel';
function contactHasVisitorChannel(
contact: ILivechatContact | null | undefined,
visitor: ILivechatContactVisitorAssociation,
): contact is ILivechatContact {
return !!contact && contact.channels.some((ch: ILivechatContactChannel) => isSameChannel(ch.visitor, visitor));
} Try / catch
try {
await runMergeContacts(null, contactId, visitor, session);
} catch (e) {
if (e instanceof Error && e.message === 'error-invalid-channel') {
// visitor no longer tied to this contact: re-resolve current contactId and retry once
}
throw e;
} Prevention
- Always resolve the contact from the visitor association at call time instead of caching contactId across requests
- Serialize merges for the same visitor to avoid concurrent-merge races
- Treat 'error-invalid-contact' and 'error-invalid-channel' as signals to refresh the contact-visitor mapping
When it happens
Trigger: Calling mergeContacts(contactId, visitor, session) where visitor.visitorId/visitor.source is not present in originalContact.channels: a stale contactId captured before a previous merge moved the channel to another contact, a visitorId/source mismatch, or a concurrent verification flow that already merged that channel away.
Common situations: Contact-visitor association drift after earlier merges, clients re-using an old contactId, duplicate/concurrent verifyContactChannel executions for the same visitor, or a source value mismatch (e.g. custom channel source vs 'rocket.chat').
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- error-invalid-contact
- error-removing-visitor
- error-verifying-contact-channel
- error-action-not-allowed
- error-contact-channel-blocked
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/929ab0d868923d7e.
Report an issue: GitHub.
Appendix: source
Thrown at apps/meteor/ee/server/patches/mergeContacts.ts:25
import { notifyOnSettingChanged } from '../../../server/lib/notifyListener';
import { ContactMerger } from '../../../server/lib/omnichannel/contacts/ContactMerger';
import { mergeContacts } from '../../../server/lib/omnichannel/contacts/mergeContacts';
import { contactLogger as logger } from '../lib/omnichannel/logger';
export const runMergeContacts = async (
_next: any,
contactId: string,
visitor: ILivechatContactVisitorAssociation,
session?: ClientSession,
): Promise<ILivechatContact | null> => {
const originalContact = await LivechatContacts.findOneEnabledById(contactId, { session });
if (!originalContact) {
throw new Error('error-invalid-contact');
}
const channel = originalContact.channels.find((channel: ILivechatContactChannel) => isSameChannel(channel.visitor, visitor));
if (!channel) {
throw new Error('error-invalid-channel');
}
logger.debug({ msg: 'Getting similar contacts', contactId });
const similarContacts: ILivechatContact[] = await LivechatContacts.findSimilarVerifiedContacts(channel, contactId, { session });
if (!similarContacts.length) {
logger.debug({ msg: 'No similar contacts found', contactId });
return originalContact;
}
logger.debug({ msg: 'Found contacts to merge', contactId, count: similarContacts.length });
for await (const similarContact of similarContacts) {
const fields = ContactMerger.getAllFieldsFromContact(similarContact);
await ContactMerger.mergeFieldsIntoContact({ fields, contact: originalContact, session });
}
const similarContactIds = similarContacts.map((c) => c._id);View on GitHub (pinned to b2c16d5842)