RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-user

error-invalid-user

Error message

Invalid user

What it means

readMessages marks a room (and optionally its threads) as read and is strictly per-user: Meteor.userId() returning null throws error-invalid-user before any room lookup. Read receipts and unread counters are keyed to the logged-in user, so an anonymous call is meaningless and rejected.

Source

Thrown at apps/meteor/server/meteor-methods/messages/readMessages.ts:23

import { Meteor } from 'meteor/meteor';

import { canAccessRoomAsync } from '../../lib/authorization';
import { readMessages } from '../../lib/readMessages';

declare module '@rocket.chat/ddp-client' {
	// eslint-disable-next-line @typescript-eslint/naming-convention
	interface ServerMethods {
		readMessages(rid: string, readThreads?: boolean): Promise<void>;
	}
}

Meteor.methods<ServerMethods>({
	async readMessages(rid, readThreads = false) {
		check(rid, String);

		const userId = Meteor.userId();
		if (!userId) {
			throw new Meteor.Error('error-invalid-user', 'Invalid user', {
				method: 'readMessages',
			});
		}

		const user = ((await Meteor.userAsync()) as IUser | null) ?? undefined;
		const room = await Rooms.findOneById(rid);
		if (!room) {
			throw new Meteor.Error('error-room-does-not-exist', 'This room does not exist', { method: 'readMessages' });
		}
		if (!(await canAccessRoomAsync(room, user))) {
			throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'readMessages' });
		}

		await readMessages(room, userId, readThreads);
	},
});

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Check Meteor.userId() before calling and skip when logged out
  2. Cancel pending read calls on logout (cleanup in component unmount / auth-change handlers)
  3. Re-authenticate and let the normal flow re-mark the room read

Example fix

// before
useEffect(() => {
  Meteor.call('readMessages', rid);
}, [rid]);

// after
useEffect(() => {
  if (!Meteor.userId()) return;
  Meteor.call('readMessages', rid);
}, [rid]);
Defensive patterns

Strategy: validation

Validate before calling

if (!Meteor.userId()) {
  // marking as read requires a logged-in user — skip the call
}

Try / catch

try {
  await Meteor.callAsync('readMessages', rid, readThreads);
} catch (error) {
  if (error instanceof Meteor.Error && error.error === 'error-invalid-user') {
    // ignore: no session — the next login will re-mark rooms read
  } else {
    throw error;
  }
}

Prevention

When it happens

Trigger: Meteor.call('readMessages', rid, readThreads) fired by an on-mount effect in a logged-out state, after logout while the room component was still mounted, or from an unauthenticated DDP client.

Common situations: Mark-as-read effects racing logout; components that keep firing read calls after the session expired; automation scripts trying to clear unread flags without logging in.

Related errors


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