RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-subscription

error-invalid-subscription

Error message

Invalid subscription

What it means

Thrown by POST rooms.saveDraft when Subscriptions.updateDraftByRoomIdAndUserId matched zero rows — i.e. the calling user has no subscription (membership) for that room. Drafts are stored on the membership record, so no membership means no draft can be saved.

Source

Thrown at apps/meteor/server/api/v1/rooms.ts:463

	{
		authRequired: true,
		body: saveDraftBodySchema,
		response: {
			200: saveDraftResponseSchema,
			400: validateBadRequestErrorResponse,
			401: validateUnauthorizedErrorResponse,
		},
	},
	async function action() {
		const { rid, draft, tmid } = this.bodyParams;

		if (draft.length > (settings.get<number>('Message_MaxAllowedSize') ?? 0)) {
			return API.v1.failure('error-message-size-exceeded');
		}

		const subscription = await Subscriptions.updateDraftByRoomIdAndUserId(rid, this.userId, draft || undefined, tmid);
		if (!subscription) {
			throw new Meteor.Error('error-invalid-subscription', 'Invalid subscription');
		}

		void notifyOnSubscriptionChanged(subscription);

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

API.v1.post(
	'rooms.cleanHistory',
	{
		authRequired: true,
		body: isRoomsCleanHistoryProps,
		response: {
			200: ajv.compile<{ _id: string; count: number }>({
				type: 'object',
				properties: {
					_id: { type: 'string' },

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Verify membership before saving the draft (subscriptions.getOne?roomId=).
  2. If the user has left, suppress draft-save calls from the client for that room.
  3. For bots, ensure the bot user is added to the room before invoking saveDraft.

Example fix

// before
await rest.post('/api/v1/rooms.saveDraft', { rid, draft });

// after
const sub = await rest.get(`/api/v1/subscriptions.getOne?roomId=${rid}`);
if (sub.subscription) {
  await rest.post('/api/v1/rooms.saveDraft', { rid, draft });
}
Defensive patterns

Strategy: validation

Validate before calling

const { subscription } = await rest.get(`/api/v1/subscriptions.getOne?roomId=${rid}`);
if (!subscription) {
  // user isn't a member — skip draft save silently
  return;
}
await rest.post('/api/v1/rooms.saveDraft', { rid, draft, tmid });

Type guard

function isRoomMember(sub: unknown): boolean {
  return !!sub && typeof (sub as any).rid === 'string';
}

Try / catch

try {
  await rest.post('/api/v1/rooms.saveDraft', { rid, draft, tmid });
} catch (e) {
  if (isMeteorError(e, 'error-invalid-subscription')) {
    // leave the draft local; user is not a member
  } else throw e;
}

Prevention

When it happens

Trigger: POST /api/v1/rooms.saveDraft with { rid, draft, tmid } for a room the authenticated user is not a member of, has left, or was removed from.

Common situations: User left the channel but the client still has it open; bots calling on behalf of users who aren't members; dry-run/test accounts; race after a kick/ban.

Related errors


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