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
- Confirm the room exists (rooms.info) before calling subscriptions.read.
- Drop stale room ids from the client's open-room list when rooms are deleted.
- 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
- Confirm the room exists before marking read.
- Drop deleted rooms from client state on push events.
- Upstream fix: throw new Meteor.Error('error-invalid-room', ...) and check room, not subscription.
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
- error-roomId-param-invalid
- User not subscribed to room
- Room not found
- not-subscribed-room
- error-room-param-not-provided
AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12).
Data as JSON: /api/errors/f2d759427897f643.
Report an issue: GitHub.