RocketChat/Rocket.Chat · error · Meteor.Error

error-subscription-not-found

error-subscription-not-found

Error message

Subscription not found

What it means

unreadMessages requires the caller to have a subscription with a last-seen timestamp in the message's room: Subscriptions.findOneByRoomIdAndUserId(rid, userId)?.ls. A missing subscription (or one without ls) throws Meteor.Error('error-subscription-not-found', 'Subscription not found') - without ls the server cannot compute the unread window.

Source

Thrown at apps/meteor/server/lib/messaging/unread/unreadMessages.ts:63

		});
	}

	const originalMessage = await Messages.findOneById(firstUnreadMessage._id, {
		projection: {
			u: 1,
			rid: 1,
			ts: 1,
		},
	});
	if (!originalMessage || userId === originalMessage.u._id) {
		throw new Meteor.Error('error-action-not-allowed', 'Not allowed', {
			method: 'unreadMessages',
			action: 'Unread_messages',
		});
	}
	const lastSeen = (await Subscriptions.findOneByRoomIdAndUserId(originalMessage.rid, userId))?.ls;
	if (!lastSeen) {
		throw new Meteor.Error('error-subscription-not-found', 'Subscription not found', {
			method: 'unreadMessages',
			action: 'Unread_messages',
		});
	}

	if (originalMessage.ts >= lastSeen) {
		return logger.debug('Provided message is already marked as unread');
	}

	logger.debug({
		msg: 'Updating unread message as the first unread',
		timestamp: originalMessage.ts,
	});
	const setAsUnreadResponse = await Subscriptions.setAsUnreadByRoomIdAndUserId(originalMessage.rid, userId, originalMessage.ts);
	if (setAsUnreadResponse.modifiedCount) {
		void notifyOnSubscriptionChangedByRoomIdAndUserId(originalMessage.rid, userId);
	}
};

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Ensure the user joined the room (subscription exists) before enabling mark-unread
  2. Refresh subscriptions after kick/remove events and hide such rooms
  3. Join the room first when acting on previewed public channels

Example fix

// before
Meteor.call('unreadMessages', { _id: messageId }); // not a member

// after
const sub = Subscriptions.findOne({ rid: message.rid });
if (sub) {
  Meteor.call('unreadMessages', { _id: messageId });
} else {
  Meteor.call('joinRoom', message.rid);
Defensive patterns

Strategy: validation

Validate before calling

const sub = await Subscriptions.findOneByRoomIdAndUserId(message.rid, userId);
if (!sub?.ls) {
  return; // no membership/last-seen: unread is not applicable
}
await unreadMessages(userId, { _id: message._id });

Try / catch

Meteor.call('unreadMessages', { _id }, undefined, (err) => {
  if (err?.error === 'error-subscription-not-found') {
    hideRoomAndResync(message.rid); // user is not a member anymore
  }
});

Prevention

When it happens

Trigger: Marking a message unread in a room the user has no subscription for: never joined, was removed, or only previewed a public channel without joining; calling with a message id belonging to a room the account cannot see.

Common situations: Stale UI offering unread for rooms the user was removed from; directory/preview flows where the room is visible but not joined; race between joining and acting.

Related errors


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