{"record":{"id":"63ac4a7e1f15a9c6","repo":"RocketChat/Rocket.Chat","slug":"error-user-already-moderator","errorCode":"error-user-already-moderator","errorMessage":"User is already a moderator","messagePattern":"User is already a moderator","errorType":"error_code","errorClass":"Meteor.Error","httpStatus":null,"severity":"warning","filePath":"apps/meteor/server/meteor-methods/rooms/addRoomModerator.ts","lineNumber":64,"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: 'addRoomModerator',\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-user-not-in-room', 'User is not in this room', {\n\t\t\tmethod: 'addRoomModerator',\n\t\t});\n\t}\n\n\tif (subscription.roles && Array.isArray(subscription.roles) === true && subscription.roles.includes('moderator') === true) {\n\t\tthrow new Meteor.Error('error-user-already-moderator', 'User is already a moderator', {\n\t\t\tmethod: 'addRoomModerator',\n\t\t});\n\t}\n\n\tawait beforeChangeRoomRole.run({ fromUserId, userId, room, role: 'moderator' });\n\n\tconst addRoleResponse = await Subscriptions.addRoleById(subscription._id, 'moderator');\n\tawait syncRoomRolePriorityForUserAndRoom(userId, rid, subscription.roles?.concat(['moderator']) || ['moderator']);\n\n\tif (addRoleResponse.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: 'addRoomLeader',\n\t\t});","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/meteor-methods/rooms/addRoomModerator.ts#L46-L82","documentation":"If the target's subscription already includes 'moderator' in its roles array, addRoomModerator throws error-user-already-moderator rather than rewriting the role. Like the leader variant this is an idempotency guard: the requested end state already holds, but the call still rejects, and it fires before beforeChangeRoomRole runs.","triggerScenarios":"Double-submitting the 'Set as moderator' action; two admins promoting the same user simultaneously; UI state stale relative to the subscription's roles.","commonSituations":"Buttons lacking pending/disabled handling; missed websocket updates making the role look absent; retry logic re-firing a completed request.","solutions":["Treat the error as success and refresh the member's roles in the UI.","Check current roles before showing the promote action.","Guard against double submission while a role change is in flight."],"exampleFix":"// before\nawait Meteor.callAsync('addRoomModerator', rid, userId);\n\n// after\ntry {\n\tawait Meteor.callAsync('addRoomModerator', rid, userId);\n} catch (e: any) {\n\tif (e?.error === 'error-user-already-moderator') return; // idempotent success\n\tthrow e;\n}","handlingStrategy":"validation","validationCode":"// skip the call when the member already carries the role\nconst member = await fetch(`/api/v1/channels.members?roomId=${rid}`, { headers }).then((r) => r.json());\nconst alreadyModerator = member.members?.find((m: any) => m._id === userId)?.roles?.includes('moderator');\nif (alreadyModerator) { /* nothing to do */ }","typeGuard":null,"tryCatchPattern":"try {\n\tawait Meteor.callAsync('addRoomModerator', rid, userId);\n} catch (e: any) {\n\tif (e?.error === 'error-user-already-moderator') return; // idempotent success\n\tthrow e;\n}","preventionTips":["Read the subscription's current roles before offering the promote action.","Guard against double-submits while a role change is pending.","Map already-role errors to success in retry/idempotency wrappers."],"tags":["meteor-methods","rooms","roles","idempotency"],"backgroundTag":"duplicate-role-assignment","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}