{"record":{"id":"6521a3e091236dbb","repo":"RocketChat/Rocket.Chat","slug":"error-invalid-user-6521a3","errorCode":"error-invalid-user","errorMessage":"Invalid user","messagePattern":"Invalid user","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/meteor-methods/messages/readMessages.ts","lineNumber":23,"sourceCode":"import { Meteor } from 'meteor/meteor';\n\nimport { canAccessRoomAsync } from '../../lib/authorization';\nimport { readMessages } from '../../lib/readMessages';\n\ndeclare module '@rocket.chat/ddp-client' {\n\t// eslint-disable-next-line @typescript-eslint/naming-convention\n\tinterface ServerMethods {\n\t\treadMessages(rid: string, readThreads?: boolean): Promise<void>;\n\t}\n}\n\nMeteor.methods<ServerMethods>({\n\tasync readMessages(rid, readThreads = false) {\n\t\tcheck(rid, String);\n\n\t\tconst userId = Meteor.userId();\n\t\tif (!userId) {\n\t\t\tthrow new Meteor.Error('error-invalid-user', 'Invalid user', {\n\t\t\t\tmethod: 'readMessages',\n\t\t\t});\n\t\t}\n\n\t\tconst user = ((await Meteor.userAsync()) as IUser | null) ?? undefined;\n\t\tconst room = await Rooms.findOneById(rid);\n\t\tif (!room) {\n\t\t\tthrow new Meteor.Error('error-room-does-not-exist', 'This room does not exist', { method: 'readMessages' });\n\t\t}\n\t\tif (!(await canAccessRoomAsync(room, user))) {\n\t\t\tthrow new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'readMessages' });\n\t\t}\n\n\t\tawait readMessages(room, userId, readThreads);\n\t},\n});\n","sourceCodeStart":5,"sourceCodeEnd":40,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/meteor-methods/messages/readMessages.ts#L5-L40","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check Meteor.userId() before calling and skip when logged out","Cancel pending read calls on logout (cleanup in component unmount / auth-change handlers)","Re-authenticate and let the normal flow re-mark the room read"],"exampleFix":"// before\nuseEffect(() => {\n  Meteor.call('readMessages', rid);\n}, [rid]);\n\n// after\nuseEffect(() => {\n  if (!Meteor.userId()) return;\n  Meteor.call('readMessages', rid);\n}, [rid]);","handlingStrategy":"validation","validationCode":"if (!Meteor.userId()) {\n  // marking as read requires a logged-in user — skip the call\n}","typeGuard":null,"tryCatchPattern":"try {\n  await Meteor.callAsync('readMessages', rid, readThreads);\n} catch (error) {\n  if (error instanceof Meteor.Error && error.error === 'error-invalid-user') {\n    // ignore: no session — the next login will re-mark rooms read\n  } else {\n    throw error;\n  }\n}","preventionTips":["Guard mark-as-read effects with Meteor.userId()","Cancel pending read calls on logout/unmount","Fire read calls only after the room is actually visible to an authenticated user"],"tags":["authentication","read-receipts","meteor-method","session"],"backgroundTag":"not-authenticated","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}