{"record":{"id":"b6d82b9a3a2650d0","repo":"RocketChat/Rocket.Chat","slug":"error-user-not-found","errorCode":null,"errorMessage":"error-user-not-found","messagePattern":"error-user-not-found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/ee/server/lib/syncUserRoles.ts","lineNumber":63,"sourceCode":"\t\tvoid api.broadcast('user.roleUpdate', {\n\t\t\ttype,\n\t\t\t_id: roleId,\n\t\t\tu: {\n\t\t\t\t_id,\n\t\t\t\tusername,\n\t\t\t},\n\t\t});\n\t}\n}\n\nexport async function syncUserRoles(\n\tuid: IUser['_id'],\n\tnewRoleList: Array<IRole['_id']>,\n\t{ allowedRoles, skipRemovingRoles, scope }: setUserRolesOptions,\n): Promise<void> {\n\tconst user = await Users.findOneById<Pick<IUser, '_id' | 'username' | 'roles'>>(uid, { projection: { username: 1, roles: 1 } });\n\tif (!user) {\n\t\tthrow new Error('error-user-not-found');\n\t}\n\n\tconst existingRoles = user.roles;\n\tconst rolesToAdd = filterRoleList(newRoleList, existingRoles, allowedRoles);\n\tconst rolesToRemove = filterRoleList(existingRoles, newRoleList, allowedRoles);\n\n\tif (!rolesToAdd.length && !rolesToRemove.length) {\n\t\treturn;\n\t}\n\n\tconst wasGuest = existingRoles.length === 1 && existingRoles[0] === 'guest';\n\tif (wasGuest && (await License.shouldPreventAction('activeUsers'))) {\n\t\tthrow new Error('error-license-user-limit-reached');\n\t}\n\n\tif (rolesToAdd.length && (await addUserRolesAsync(uid, rolesToAdd, scope))) {\n\t\tbroadcastRoleChange('added', rolesToAdd, user);\n\t}","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/ee/server/lib/syncUserRoles.ts#L45-L81","documentation":"syncUserRoles loads the target with Users.findOneById(uid, { projection: { username: 1, roles: 1 } }); if the document is gone it throws Error('error-user-not-found'). All subsequent logic - role diffing, the guest/license check, add/remove operations - happens only after this lookup succeeds.","triggerScenarios":"Calling syncUserRoles with a uid for a user that was deleted (deletion raced the role sync), a malformed uid, or an id that belongs to a different environment/workspace than the database being written.","commonSituations":"User lifecycle automation enqueueing delete and role-sync in the wrong order; imports referencing user ids that do not exist locally; ids copy-pasted between staging and production.","solutions":["Verify the user exists before enqueueing/invoking role sync, or capture deletion events to cancel pending syncs.","Make queue handlers idempotent: treat a missing user as a no-op for role synchronization.","Remove stale references to deleted user ids from jobs, scripts, and external systems."],"exampleFix":"// before\nawait syncUserRoles(uid, newRoles, opts); // throws error-user-not-found for deleted users\n\n// after\nconst user = await Users.findOneById(uid, { projection: { _id: 1 } });\nif (!user) return; // user gone; role sync is moot\nawait syncUserRoles(uid, newRoles, opts);","handlingStrategy":"validation","validationCode":"const user = await Users.findOneById(uid, { projection: { _id: 1 } });\nif (!user) {\n\t// user deleted or wrong id; skip the sync instead of calling syncUserRoles\n}","typeGuard":null,"tryCatchPattern":"try {\n\tawait syncUserRoles(uid, newRoleList, opts);\n} catch (e: any) {\n\tif (e?.message === 'error-user-not-found') { dropStaleJob(uid); return; } // no user left to sync\n\tthrow e;\n}","preventionTips":["Order lifecycle operations: cancel pending role syncs when a user is deleted.","Make queue handlers idempotent so a missing user is a no-op, not a poison message.","Never reuse user ids across environments; resolve ids per workspace."],"tags":["roles","users","entity-not-found","enterprise"],"backgroundTag":"entity-not-found","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}