RocketChat/Rocket.Chat · error · Error

error-invalid-subscription

error-invalid-subscription

Error message

error-invalid-subscription

What it means

Plain Error('error-invalid-subscription') thrown by readMessages when Subscriptions.findOneByRoomIdAndUserId returns no document for the room+user pair. It means the caller tried to mark a room as read for a user who has no subscription in that room.

Source

Thrown at apps/meteor/server/lib/readMessages.ts:13

import type { IRoom, IUser } from '@rocket.chat/core-typings';
import { NotificationQueue, Subscriptions } from '@rocket.chat/models';

import { callbacks } from './callbacks';
import { notifyOnSubscriptionChangedByRoomIdAndUserId } from './notifyListener';

export async function readMessages(room: IRoom, uid: IUser['_id'], readThreads: boolean): Promise<void> {
	await callbacks.run('beforeReadMessages', room._id, uid);

	const projection = { ls: 1, tunread: 1, alert: 1, ts: 1 };
	const sub = await Subscriptions.findOneByRoomIdAndUserId(room._id, uid, { projection });
	if (!sub) {
		throw new Error('error-invalid-subscription');
	}

	// do not mark room as read if there are still unread threads
	const alert = !!(sub.alert && !readThreads && sub.tunread && sub.tunread.length > 0);

	const setAsReadResponse = await Subscriptions.setAsReadByRoomIdAndUserId(room._id, uid, readThreads, alert);
	if (setAsReadResponse.modifiedCount) {
		void notifyOnSubscriptionChangedByRoomIdAndUserId(room._id, uid);
	}

	await NotificationQueue.clearQueueByUserId(uid);

	const lastSeen = sub.ls || sub.ts;
	callbacks.runAsync('afterReadMessages', room, { uid, lastSeen });
}

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Treat it as stale state: reload the user's subscription list and close the room view that produced the call.
  2. Before calling, confirm a subscription exists for the (roomId, userId) pair.
  3. In automation, verify the user is still a member before marking rooms read.

Example fix

// before
await readMessages(room, uid, false); // throws error-invalid-subscription

// after
const sub = await Subscriptions.findOneByRoomIdAndUserId(room._id, uid, { projection: { _id: 1 } });
if (sub) {
  await readMessages(room, uid, false);
}
Defensive patterns

Strategy: validation

Validate before calling

// server-side, before calling readMessages
const sub = await Subscriptions.findOneByRoomIdAndUserId(roomId, uid, { projection: { _id: 1 } });
if (!sub) {
  // user has no subscription here: refresh state and skip mark-read
}

Try / catch

try {
  await readMessages(room, uid, readThreads);
} catch (error: any) {
  if (error?.message === 'error-invalid-subscription') {
    // stale state, not fatal: reload subscriptions and drop the room view
  }
}

Prevention

When it happens

Trigger: Invoking the mark-read flow for a room the user never joined, or for a room whose subscription was already deleted (kick, leave, federation removal) while the client still had the room open.

Common situations: Stale browser tab of a room the user was kicked from; race between leaving a room and a pending mark-read request; automation calling subscriptions.read with stale membership data.

Related errors


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