{"record":{"id":"3e1d6a20d4bc7787","repo":"RocketChat/Rocket.Chat","slug":"error-invalid-subscription-3e1d6a","errorCode":"error-invalid-subscription","errorMessage":"error-invalid-subscription","messagePattern":"error-invalid-subscription","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/lib/rooms/executeUnbanUserFromRoom.ts","lineNumber":20,"sourceCode":"import { isBannedSubscription, isInviteSubscription, type IUser } from '@rocket.chat/core-typings';\nimport { Rooms, Subscriptions, Users } from '@rocket.chat/models';\n\nimport { afterUnbanFromRoomCallback } from '../callbacks/afterUnbanFromRoomCallback';\nimport { notifyOnRoomChangedById, notifyOnSubscriptionChanged } from '../notifyListener';\n\nexport const executeUnbanUserFromRoom = async function (rid: string, user: IUser, byUser: IUser): Promise<void> {\n\tconst room = await Rooms.findOneById(rid);\n\tif (!room) {\n\t\tthrow new Error('error-invalid-room');\n\t}\n\n\tif (!user.username) {\n\t\tthrow new Error('error-invalid-user');\n\t}\n\n\tconst subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, user._id);\n\tif (!subscription) {\n\t\tthrow new Error('error-invalid-subscription');\n\t}\n\n\t// if the subscription is an invite it means we were unbanned and then invited again, then\n\t// the invite was accepted and we receive a leave event (meaning the user was unbanned), so\n\t// at this point we just need send the message to say the user was unbanned.\n\tif (isInviteSubscription(subscription)) {\n\t\tawait Message.saveSystemMessage('user-unbanned', rid, user.username, user, {\n\t\t\tu: { _id: byUser._id, username: byUser.username },\n\t\t});\n\n\t\treturn;\n\t}\n\n\t// if the subscription exists and is not an invite and not banned\n\tif (!isBannedSubscription(subscription)) {\n\t\tthrow new Error('error-user-not-banned');\n\t}\n","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/lib/rooms/executeUnbanUserFromRoom.ts#L2-L38","documentation":"Before unbanning, the code looks up the target's subscription with Subscriptions.findOneByRoomIdAndUserId(rid, user._id). No subscription means the user is not associated with the room at all (banning removes the subscription differently in this design, so a banned user still has one), hence Error 'error-invalid-subscription'.","triggerScenarios":"Unbanning a user who was never in the room, whose subscription was already removed (e.g. unban executed twice, or ban flow deleted the doc), or passing mismatched rid/user._id pairs.","commonSituations":"Double-click / duplicate unban requests racing after the first one removed state; UI dropdown showing stale banned-user lists after another admin already unbanned; federation events out of order.","solutions":["Treat as idempotent: pre-check the subscription (or catch the error) and report 'already unbanned'","Disable the unban action after first submission to prevent duplicate requests","Refresh the banned-users list from subscription state before offering unban"],"exampleFix":"// before\nMeteor.call('unbanUserFromRoom', rid, userId);\n\n// after\nconst sub = await Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { _id: 1 } });\nif (!sub) return; // already unbanned / never a member\nMeteor.call('unbanUserFromRoom', rid, userId);","handlingStrategy":"validation","validationCode":"const sub = await Subscriptions.findOneByRoomIdAndUserId(rid, user._id, { projections: { _id: 1 } });\nif (!sub) {\n  // user already fully removed — treat as success (idempotent unban)\n  return;\n}\nawait executeUnbanUserFromRoom(rid, user, byUser);","typeGuard":null,"tryCatchPattern":"try {\n  await executeUnbanUserFromRoom(rid, user, byUser);\n} catch (err) {\n  if (err instanceof Error && err.message === 'error-invalid-subscription') {\n    // idempotent case — already unbanned/removed; no-op\n  }\n  throw err;\n}","preventionTips":["Make unban actions idempotent by pre-checking subscription existence","Disable the unban UI action immediately after success","Debounce double-submit on moderation buttons"],"tags":["unban","subscription","validation","race-condition"],"backgroundTag":"stale-subscription-state","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}