RocketChat/Rocket.Chat · error · Error

User not subscribed to room

Error message

User not subscribed to room

What it means

Thrown by getUnreadByUser when Subscriptions.findOneByRoomIdAndUserId returns no document for the given room/user pair. In Rocket.Chat a user must have an active subscription (membership) to a room before message-read state is tracked, so the bridge refuses to compute unread messages for a non-member.

Source

Thrown at apps/meteor/app/apps/server/bridges/rooms.ts:330

		const users = await Users.findByIds(subs.map((user: { uid: string }) => user.uid)).toArray();
		const userConverter = this.orch.getConverters().get('users');
		return users.map((user: ICoreUser) => userConverter.convertToApp(user));
	}

	protected async getUnreadByUser(roomId: string, uid: string, options: GetMessagesOptions, appId: string): Promise<Array<IMessageRaw>> {
		this.orch.debugLog(`The App ${appId} is getting the unread messages for the user: "${uid}" in the room: "${roomId}"`);

		const messageConverter = this.orch.getConverters()?.get('messages');
		if (!messageConverter) {
			throw new Error('Message converter not found');
		}

		const subscription = await Subscriptions.findOneByRoomIdAndUserId(roomId, uid, { projection: { ls: 1 } });

		if (!subscription) {
			const errorMessage = `No subscription found for user with ID "${uid}" in room with ID "${roomId}". This means the user is not subscribed to the room.`;
			this.orch.debugLog(errorMessage);
			throw new Error('User not subscribed to room');
		}

		const lastSeen = subscription?.ls;
		if (!lastSeen) {
			return [];
		}

		const sort: Sort = options.sort?.createdAt ? { ts: options.sort.createdAt } : { ts: 1 };

		const cursor = Messages.findVisibleByRoomIdBetweenTimestampsNotContainingTypes(
			roomId,
			lastSeen,
			new Date(),
			[],
			{
				limit: options.limit,
				skip: options.skip,
				sort,

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Filter the user list to actual room members before calling getUnreadByUser (use the room's membership readers).
  2. Wrap the call per-user in try/catch and skip users who are not subscribed.
  3. Re-fetch membership right before the call if the room's roster can change concurrently.

Example fix

// before
for (const uid of allUserIds) {
  const unread = await read.getRoomReader().getUnreadByUser(roomId, uid);
}

// after
for (const uid of allUserIds) {
  try {
    const unread = await read.getRoomReader().getUnreadByUser(roomId, uid);
  } catch (err) {
    // user is not a member of the room; skip
    continue;
  }
}
Defensive patterns

Strategy: validation

Validate before calling

const members = await read.getRoomReader().getMembers(roomId);
const memberIds = new Set(members.map((m) => m.id));
const safeUserIds = allUserIds.filter((uid) => memberIds.has(uid));

Try / catch

for (const uid of allUserIds) {
  try {
    const unread = await read.getRoomReader().getUnreadByUser(roomId, uid);
  } catch (err) {
    if (err instanceof Error && err.message === 'User not subscribed to room') {
      continue;
    }
    throw err;
  }
}

Prevention

When it happens

Trigger: An app calls getUnreadByUser(roomId, uid) for a user who has never joined the room, was removed from it, or whose subscription was deleted. The debug log records the exact room and user ids.

Common situations: App iterates a list of user ids that includes non-members; user left or was kicked between the app gathering the list and reading unread counts; livechat visitors or bot users queried against a room they are not subscribed to.

Related errors


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