{"record":{"id":"da70f4d61fa09140","repo":"RocketChat/Rocket.Chat","slug":"error-user-not-moderator","errorCode":"error-user-not-moderator","errorMessage":"User is not a moderator","messagePattern":"User is not a moderator","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/meteor-methods/rooms/removeRoomModerator.ts","lineNumber":57,"sourceCode":"\n\tconst user = await Users.findOneById(userId);\n\n\tif (!user?.username) {\n\t\tthrow new Meteor.Error('error-invalid-user', 'Invalid user', {\n\t\t\tmethod: 'removeRoomModerator',\n\t\t});\n\t}\n\n\tconst subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, user._id);\n\n\tif (!subscription) {\n\t\tthrow new Meteor.Error('error-invalid-room', 'Invalid room', {\n\t\t\tmethod: 'removeRoomModerator',\n\t\t});\n\t}\n\n\tif (subscription.roles && (!Array.isArray(subscription.roles) || !subscription.roles.includes('moderator'))) {\n\t\tthrow new Meteor.Error('error-user-not-moderator', 'User is not a moderator', {\n\t\t\tmethod: 'removeRoomModerator',\n\t\t});\n\t}\n\n\tawait beforeChangeRoomRole.run({ fromUserId, userId, room, role: 'user' });\n\n\tconst removeRoleResponse = await Subscriptions.removeRoleById(subscription._id, 'moderator');\n\tawait syncRoomRolePriorityForUserAndRoom(userId, rid, subscription.roles?.filter((r) => r !== 'moderator') || []);\n\n\tif (removeRoleResponse.modifiedCount) {\n\t\tvoid notifyOnSubscriptionChangedById(subscription._id);\n\t}\n\n\tconst fromUser = await Users.findOneById(fromUserId);\n\tif (!fromUser) {\n\t\tthrow new Meteor.Error('error-invalid-user', 'Invalid user', {\n\t\t\tmethod: 'removeRoomModerator',\n\t\t});","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/meteor-methods/rooms/removeRoomModerator.ts#L39-L75","documentation":"removeRoomModerator only proceeds when the target's subscription carries the 'moderator' role. If subscription.roles is truthy but either not an array or does not include 'moderator', it throws error-user-not-moderator. Quirk: a null/undefined roles field slips past this check.","triggerScenarios":"Calling removeRoomModerator on a plain member or an owner-only user; the moderator role was already stripped by another admin; subscription.roles holds a corrupted non-array truthy value.","commonSituations":"Members list still showing a stale 'Moderator' badge after the role was removed elsewhere; double-click racing the UI into issuing the demotion twice.","solutions":["Read the target's subscription roles first and only offer 'Remove moderator' when the array includes 'moderator'","Refresh room members before rendering role actions","Catch error-user-not-moderator and treat it as 'already in the desired state'"],"exampleFix":"// before\nawait Meteor.callAsync('removeRoomModerator', rid, userId);\n\n// after\nconst sub = Subscriptions.findOne({ rid, 'u._id': userId });\nconst roles = sub?.roles;\nif (!Array.isArray(roles) || !roles.includes('moderator')) return;\nawait Meteor.callAsync('removeRoomModerator', rid, userId);","handlingStrategy":"validation","validationCode":"const sub = Subscriptions.findOne({ rid, 'u._id': userId });\nif (!Array.isArray(sub?.roles) || !sub.roles.includes('moderator')) {\n  // not a moderator: skip or mark as already demoted\n}","typeGuard":"const hasRoomRole = (roles: string[] | undefined | null, role: string): boolean =>\n  Array.isArray(roles) && roles.includes(role);\n\nconst isModerator = (sub?: { roles?: string[] }): boolean => hasRoomRole(sub?.roles, 'moderator');","tryCatchPattern":"try {\n  await Meteor.callAsync('removeRoomModerator', rid, userId);\n} catch (err) {\n  if (err instanceof Meteor.Error && err.error === 'error-user-not-moderator') {\n    // already demoted: refresh and continue\n  }\n}","preventionTips":["Only show 'Remove moderator' when the subscription roles include moderator","Refresh roles before actions","Treat role-absence errors as no-ops in idempotent flows"],"tags":["roles","subscriptions","rooms","meteor-methods"],"backgroundTag":"role-not-assigned","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}