RocketChat/Rocket.Chat · error · Error

Invalid target agent, cannot transfer

Error message

Invalid target agent, cannot transfer

What it means

Thrown by AppLivechatBridge.transferVisitor when transferData.targetAgent.id is set but Users.findOneAgentById returns null. The bridge must resolve the target agent (id, username, name) to populate transferredTo, so an unknown or non-agent target id aborts the transfer.

Source

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

		}
		const { _id, username, name, type } = appUser;
		const transferredBy = {
			_id,
			username,
			name,
			type,
			userType: 'user',
		} as const;

		let userId;
		let transferredTo;

		if (targetAgent?.id) {
			transferredTo = await Users.findOneAgentById(targetAgent.id, {
				projection: { _id: 1, username: 1, name: 1 },
			});
			if (!transferredTo) {
				throw new Error('Invalid target agent, cannot transfer');
			}

			userId = transferredTo._id;
		}

		// #TODO: #AppsEngineTypes - Remove explicit types and typecasts once the apps-engine definition/implementation mismatch is fixed.
		return transfer(
			(await this.orch.getConverters()?.get('rooms').convertAppRoom(currentRoom)) as IOmnichannelRoom,
			this.orch.getConverters()?.get('visitors').convertAppVisitor(visitor),
			{ userId, departmentId, transferredBy, transferredTo },
		);
	}

	protected async findVisitors(query: object, appId: string): Promise<Array<IVisitor>> {
		this.orch.debugLog(`The App ${appId} is looking for livechat visitors.`);

		if (this.orch.isDebugging()) {
			console.warn('The method AppLivechatBridge.findVisitors is deprecated. Please consider using its alternatives');

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Validate the target agent id with the livechat read accessor (findOneAgentById) before calling transfer.
  2. Refresh cached agent ids at transfer time.
  3. If the target is optional, fall back to department-based or automatic routing when the agent is missing.
  4. Confirm the user is an active livechat agent, not just a regular user.

Example fix

// before
await app.getLivechatModifier().transferVisitor(visitor, {
  currentRoom: room,
  targetAgent: { id: cachedAgentId },
});

// after
const targetAgent = cachedAgentId
  ? await Users.findOneAgentById(cachedAgentId)
  : undefined;
if (cachedAgentId && !targetAgent) {
  throw new Error(`Target agent ${cachedAgentId} is not available`);
}
await app.getLivechatModifier().transferVisitor(visitor, {
  currentRoom: room,
  targetAgent: targetAgent ? { id: targetAgent._id } : undefined,
});
Defensive patterns

Strategy: validation

Validate before calling

if (transferData.targetAgent?.id) {
  const target = await Users.findOneAgentById(transferData.targetAgent.id);
  if (!target) {
    throw new Error(`Target agent ${transferData.targetAgent.id} does not exist or is not an agent`);
  }
}
await app.getLivechatModifier().transferVisitor(visitor, transferData);

Type guard

const isResolvableTargetAgent = async (id: string): Promise<boolean> =>
  Boolean(await Users.findOneAgentById(id, { projection: { _id: 1 } }));

Try / catch

try {
  return await app.getLivechatModifier().transferVisitor(visitor, transferData);
} catch (e) {
  if ((e as Error).message === 'Invalid target agent, cannot transfer') {
    // fall back to department-based or automatic routing
  }
  throw e;
}

Prevention

When it happens

Trigger: An App calls transfer with a targetAgent whose id does not resolve to an agent: the id is stale/deleted, belongs to a non-agent user, or was mistyped. findOneAgentById enforces both existence and agent status.

Common situations: App caches a target agent id that was later removed/demoted; App passes a username or token where an agent user id is expected; agent was removed from the livechat agents collection but not the Users collection; transfer initiated from stale UI state.

Related errors


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