{"record":{"id":"76e126b54a3d5dc3","repo":"RocketChat/Rocket.Chat","slug":"error-user-already-owner","errorCode":"error-user-already-owner","errorMessage":"User is already an owner","messagePattern":"User is already an owner","errorType":"error_code","errorClass":"Meteor.Error","httpStatus":null,"severity":"warning","filePath":"apps/meteor/server/meteor-methods/rooms/addRoomOwner.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: 'addRoomOwner',\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: 'addRoomOwner',\n\t\t});\n\t}\n\n\tif (subscription.roles && Array.isArray(subscription.roles) === true && subscription.roles.includes('owner') === true) {\n\t\tthrow new Meteor.Error('error-user-already-owner', 'User is already an owner', {\n\t\t\tmethod: 'addRoomOwner',\n\t\t});\n\t}\n\n\tawait beforeChangeRoomRole.run({ fromUserId, userId, room, role: 'owner' });\n\n\tconst addRoleResponse = await Subscriptions.addRoleById(subscription._id, 'owner');\n\tawait syncRoomRolePriorityForUserAndRoom(userId, rid, subscription.roles?.concat(['owner']) || ['owner']);\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/addRoomOwner.ts#L46-L82","documentation":"Thrown by addRoomOwner() in apps/meteor/server/meteor-methods/rooms/addRoomOwner.ts:64 when the target's subscription already has 'owner' in its roles array. The check is explicit (Array.isArray + includes) and acts as an idempotency guard so the role is not re-added, no system message is sent, and no federation event fires for a no-op.","triggerScenarios":"Calling addRoomOwner twice for the same user; retrying after a timeout when the first call actually succeeded; UI double-submission; scripts syncing roles that do not read the current subscription.roles before writing; a previous partial run added the role and then the method was re-invoked.","commonSituations":"Retry logic without idempotency checks after network flaps; bulk role-sync jobs replaying their whole list; two admins promoting the same person simultaneously; client re-render triggering a duplicate method call.","solutions":["Treat this error as benign if it comes from a retry: verify current roles and skip instead of re-calling.","Pre-check before calling: fetch the subscription and return early when subscription.roles?.includes('owner').","Make client actions idempotent (disable the button while in-flight) so the second call never happens.","For sync jobs, reconcile by diffing current roles against desired roles rather than blindly applying."],"exampleFix":"// before\nawait addRoomOwner(uid, rid, targetUserId); // may throw error-user-already-owner\n\n// after\nconst sub = await Subscriptions.findOneByRoomIdAndUserId(rid, targetUserId, { projection: { roles: 1 } });\nif (sub?.roles?.includes('owner')) return; // already owner, no-op\nawait addRoomOwner(uid, rid, targetUserId);","handlingStrategy":"type-guard","validationCode":"const sub = await Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { roles: 1 } });\nif (sub?.roles?.includes('owner')) return; // already owner: skip instead of throwing","typeGuard":"const isAlreadyOwner = (sub: { roles?: string[] } | null): boolean =>\n  Array.isArray(sub?.roles) === true && sub!.roles!.includes('owner');","tryCatchPattern":"try {\n  await addRoomOwner(uid, rid, userId);\n} catch (e) {\n  if (e instanceof Meteor.Error && e.error === 'error-user-already-owner') {\n    // benign no-op: treat as success in idempotent flows\n    return true;\n  }\n  throw e;\n}","preventionTips":["Make promote actions idempotent: pre-read subscription.roles and short-circuit.","Disable submit buttons while a role change is in flight to prevent double calls.","In retry logic, map 'error-user-already-owner' to success instead of surfacing an error."],"tags":["meteor-method","idempotency","roles","rooms"],"backgroundTag":"user-already-has-role","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}