RocketChat/Rocket.Chat · error · Error

Invalid visitor, cannot transfer

Error message

Invalid visitor, cannot transfer

What it means

Thrown by AppLivechatBridge.transferVisitor when the visitor argument is falsy. Transfer requires a source visitor to convert and pass to the omni-core transfer function, so an absent visitor aborts before any transfer logic runs. It is a strict precondition on the transfer accessor.

Source

Thrown at apps/meteor/app/apps/server/bridges/livechat.ts:262

			id: visitor.id,
			...(visitor.phone?.length && { phone: { number: visitor.phone[0].phoneNumber } }),
			...(visitor.visitorEmails?.length && { email: visitor.visitorEmails[0].address }),
			...(externalIds?.length && { externalIds }),
		};

		const livechatVisitor = await registerGuest(registerData, {
			shouldConsiderIdleAgent: settings.get<boolean>('Livechat_enabled_when_agent_idle'),
			shouldConsiderOfflineAgent: settings.get<boolean>('Livechat_accept_chats_with_no_agents'),
		});

		return this.orch.getConverters()?.get('visitors').convertVisitor(livechatVisitor);
	}

	protected async transferVisitor(visitor: IVisitor, transferData: ILivechatTransferData, appId: string): Promise<boolean> {
		this.orch.debugLog(`The App ${appId} is transfering a livechat.`);

		if (!visitor) {
			throw new Error('Invalid visitor, cannot transfer');
		}

		const { targetAgent, targetDepartment: departmentId, currentRoom } = transferData;

		const appUser = await Users.findOneByAppId(appId, {});
		if (!appUser) {
			throw new Error('Invalid app user, cannot transfer');
		}
		const { _id, username, name, type } = appUser;
		const transferredBy = {
			_id,
			username,
			name,
			type,
			userType: 'user',
		} as const;

		let userId;

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Load the visitor via the livechat read accessor before calling transfer and fail fast if it is absent.
  2. Add a parameter check (if (!visitor) throw ...) in the App so the error message points at the real cause.
  3. Ensure the call site always has both the room and the visitor in scope.
  4. Add a type guard so undefined visitors are rejected at compile time.

Example fix

// before
await app.getLivechatModifier().transferVisitor(maybeVisitor, transferData);

// after
if (!visitor) {
  throw new Error('Cannot transfer: visitor not loaded for room ' + transferData.currentRoom.id);
}
await app.getLivechatModifier().transferVisitor(visitor, transferData);
Defensive patterns

Strategy: validation

Validate before calling

if (!visitor) {
  throw new Error('Cannot transfer: visitor was not loaded');
}
await app.getLivechatModifier().transferVisitor(visitor, transferData);

Type guard

const isLoadedVisitor = (v: IVisitor | undefined | null): v is IVisitor =>
  Boolean(v && v.token);

Try / catch

try {
  return await app.getLivechatModifier().transferVisitor(visitor, transferData);
} catch (e) {
  if ((e as Error).message === 'Invalid visitor, cannot transfer') {
    // reload visitor from the livechat read accessor and retry once
  }
  throw e;
}

Prevention

When it happens

Trigger: An App calls the livechat modifier to transfer a room (e.g. app.getLivechatModifier().transferVisitor(visitor, transferData)) with visitor being undefined or null, e.g. the App failed to fetch the visitor before transferring.

Common situations: App transfers by roomId only and forgets to load the visitor; a read accessor returned undefined for the visitor and the App forwarded it unchecked; refactoring changed the call signature and dropped the visitor argument.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/c9b80c39ba5dd620. Report an issue: GitHub.