RocketChat/Rocket.Chat · error · MeteorError
error-user-not-found
error-user-not-found
Error message
User not found
What it means
saveUser throws error-user-not-found (method 'saveUser') on the update path: userData has an _id (isUpdateUserData true) but Users.findOneById(userData._id) returned null. The target of the update does not exist; only inserts without _id, or updates against an existing record, are valid.
Source
Thrown at apps/meteor/server/lib/users/saveUser/saveUser.ts:108
let sendPassword = false;
if (userData.hasOwnProperty('setRandomPassword')) {
if (userData.setRandomPassword) {
userData.password = generatePassword();
userData.requirePasswordChange = true;
sendPassword = true;
}
delete userData.setRandomPassword;
}
if (!isUpdateUserData(userData)) {
// TODO audit new users
return saveNewUser(userData, sendPassword, performedBy);
}
if (!oldUserData) {
throw new MeteorError('error-user-not-found', 'User not found', {
method: 'saveUser',
});
}
options?.auditStore?.setOriginalUser(oldUserData);
await validateUserEditing(userId, userData);
// update user
const updater = Users.getUpdater();
if (userData.hasOwnProperty('username') || userData.hasOwnProperty('name')) {
if (
!(await saveUserIdentity({
_id: userData._id,
username: userData.username,
name: userData.name,
updateUsernameInBackground: true,View on GitHub (pinned to b2c16d5842)
Solutions
- Look the user up by a stable key (username/email) to resolve the current _id right before saving
- Treat this error as a 404 and refresh the client's copy of the user
- Never fabricate _ids for updates; omit _id to create, or query it first
Example fix
// before
await saveUser(actorId, { _id, name: 'New Name' }); // stale _id -> error-user-not-found
// after
const existing = await Users.findOneByUsernameIgnoringCase(username);
if (!existing) throw new Error('User no longer exists');
await saveUser(actorId, { _id: existing._id, name: 'New Name' }); Defensive patterns
Strategy: validation
Validate before calling
if (userData._id) {
const existing = await Users.findOneById(userData._id, { projections: { _id: 1 } });
if (!existing) throw new Error('Target user no longer exists');
}
await saveUser(actorId, userData); Type guard
const isUpdateOfExistingUser = async (id: string): Promise<boolean> =>
!!(await Users.findOneById(id, { projections: { _id: 1 } })); Try / catch
try {
await saveUser(actorId, userData);
} catch (e) {
if (isMeteorErrorCode(e, 'error-user-not-found')) {
refreshUserEditor(); // target vanished; reload instead of retrying blindly
}
} Prevention
- Resolve _id from username/email immediately before the update call
- Handle 404-style refreshes in long-lived edit forms
When it happens
Trigger: Calling saveUser with an _id that was deleted, a fabricated ObjectId from tests, or a race where the user is removed between the client loading the edit form and submitting it.
Common situations: Stale edit forms open in a browser while an admin deletes the account; tooling upserting by guessing ids instead of looking them up; cross-environment id confusion (dev id against prod data).
Related errors
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/133ae0281942ccad.
Report an issue: GitHub.