RocketChat/Rocket.Chat · error · Error

error-invalid-subscription

error-invalid-subscription

Error message

error-invalid-subscription

What it means

BUG-WARNING: this is thrown as a plain `new Error('error-invalid-subscription')`, NOT `new Meteor.Error(...)`, so it will not produce the standard REST error envelope — clients may see a generic 500 instead of a structured error. Additionally the check is on the ROOM (Rooms.findOneById returned null) for POST subscriptions.read, but the code/message say 'subscription'. Semantically: the room id (rid/roomId) you passed does not exist.

Source

Thrown at apps/meteor/server/api/v1/subscriptions.ts:147

API.v1.post(
	'subscriptions.read',
	{
		authRequired: true,
		body: isSubscriptionsReadProps,
		response: {
			200: voidSuccessResponseSchema,
			400: validateBadRequestErrorResponse,
			401: validateUnauthorizedErrorResponse,
		},
	},
	async function action() {
		const { readThreads = false } = this.bodyParams;
		const roomId = 'rid' in this.bodyParams ? this.bodyParams.rid : this.bodyParams.roomId;

		const room = await Rooms.findOneById(roomId);
		if (!room) {
			throw new Error('error-invalid-subscription');
		}

		await readMessages(room, this.userId, readThreads);

		return API.v1.success();
	},
);

API.v1.post(
	'subscriptions.unread',
	{
		authRequired: true,
		body: isSubscriptionsUnreadProps,
		response: {
			200: voidSuccessResponseSchema,
			400: validateBadRequestErrorResponse,
			401: validateUnauthorizedErrorResponse,
		},

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Confirm the room exists (rooms.info) before calling subscriptions.read.
  2. Drop stale room ids from the client's open-room list when rooms are deleted.
  3. Upstream: change `new Error(...)` to `new Meteor.Error('error-invalid-room', ...)` to match the actual condition and produce a proper REST error.

Example fix

// before
await rest.post('/api/v1/subscriptions.read', { rid });

// after
const info = await rest.get(`/api/v1/rooms.info?roomId=${rid}`);
if (!info.room) { /* room gone — drop it locally */ return; }
await rest.post('/api/v1/subscriptions.read', { rid });
Defensive patterns

Strategy: validation

Validate before calling

const info = await rest.get(`/api/v1/rooms.info?roomId=${roomId}`);
if (!info.room) {
  // room does not exist; do not call subscriptions.read
  return;
}

Type guard

function roomExists(info: { room?: unknown }): boolean {
  return !!info.room;
}

Try / catch

try {
  await rest.post('/api/v1/subscriptions.read', { rid: roomId });
} catch (e) {
  // NOTE: thrown as plain Error, not Meteor.Error — may surface as a 500
  if (String(e.message ?? e).includes('error-invalid-subscription')) {
    // room not found; drop from local state
  } else throw e;
}

Prevention

When it happens

Trigger: POST /api/v1/subscriptions.read with { rid } or { roomId } where no room matches that id. Despite the label, membership is not checked here — only room existence.

Common situations: Client holds a stale/deleted room id; room was removed between open and mark-read; typo in rid; bot acting on a room it never joined.

Related errors


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