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
- Treat it as stale state: reload the user's subscription list and close the room view that produced the call.
- Before calling, confirm a subscription exists for the (roomId, userId) pair.
- 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
- Close or drop room views when the subscription is removed (kick/leave events).
- Debounce mark-read calls when switching rooms quickly.
- Check membership before calling subscriptions.read from automation.
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
- error-invalid-subscription
- error-invalid-subscription
- User is not in this room
- User is already banned from this room
- error-not-invited
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/dca5a68aa3b77484.
Report an issue: GitHub.