RocketChat/Rocket.Chat · error · Meteor.Error

cannot-access-room

cannot-access-room

Error message

cannot-access-room

What it means

When the found room is open, getRoom compares room.v.token with guest.token. A mismatch means the caller is trying to use a room that belongs to a different visitor, so Meteor.Error('cannot-access-room') is thrown instead of returning the room.

Source

Thrown at apps/meteor/server/lib/omnichannel/rooms.ts:80

	if (room?.v._id && (await LivechatContacts.isChannelBlocked(Visitors.makeVisitorAssociation(room.v._id, room.source)))) {
		throw new Error('error-contact-channel-blocked');
	}

	if (!room?.open) {
		livechatLogger.debug({ msg: 'Last room for visitor closed. Creating new one', visitorId: guest._id });
	}

	if (!room?.open) {
		return {
			room: await createRoom({ visitor: guest, message: message.msg, roomInfo, agent, extraData }),
			newRoom: true,
		};
	}

	if (room.v.token !== guest.token) {
		livechatLogger.debug({ msg: 'Visitor trying to access another visitor room', visitorId: guest._id });
		throw new Meteor.Error('cannot-access-room');
	}

	return { room, newRoom: false };
}

export async function createRoom({
	visitor,
	message,
	rid,
	roomInfo,
	agent,
	extraData,
}: {
	visitor: ILivechatVisitor;
	message?: string;
	rid?: string;
	roomInfo: IOmnichannelRoomInfo;
	agent?: SelectedAgent;

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Always obtain a room via the visitor's own token instead of reusing a stored rid.
  2. Clear the stored rid and token together whenever the visitor session resets.
  3. In REST integrations, fetch rid from the same visitor context that owns the token (livechat visitor/room creation endpoints).

Example fix

// before
// reusing rid from another session -> cannot-access-room
const { room } = await getRoom(guest, { rid: storedRid, msg, token }, roomInfo);

// after
// drop the stale rid; let the flow find/create the room for this token
const { room, newRoom } = await getRoom(guest, { rid: makeRid(guest.token), msg, token: guest.token }, roomInfo);
Defensive patterns

Strategy: validation

Validate before calling

const room = await LivechatRooms.findOneById(message.rid, { projection: { 'v.token': 1, open: 1 } });
if (room?.open && room.v?.token !== guest.token) {
	throw new Error('rid belongs to a different visitor - request a new room');
}

Type guard

const isRoomOwnedByGuest = (room: { v?: { token?: string } } | null, guest: { token?: string }): boolean =>
	Boolean(room?.v?.token && room.v.token === guest.token);

Try / catch

try {
	const { room } = await getRoom(guest, message, roomInfo);
} catch (err) {
	if (err instanceof Meteor.Error && err.error === 'cannot-access-room') {
		// drop the stored rid and create a fresh room for this token
		return;
	}
	throw err;
}

Prevention

When it happens

Trigger: Calling the livechat flow with a rid whose room belongs to another visitor token: an rid copied from another session, a token that was regenerated (visitor record recreated / cookies cleared) while a stale rid is reused, or two widget instances sharing state.

Common situations: Persisted rid in localStorage surviving a visitor re-initialization; test scripts reusing rid/token pairs; multi-tab widgets leaking token/rid state.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/9c98bffee26e02b9. Report an issue: GitHub.