RocketChat/Rocket.Chat · error · MeteorError
error-invalid-user
error-invalid-user
Error message
Invalid user
What it means
Thrown by validateUserEditing() when Users.findOneById(userData._id) returns null — the user being updated no longer exists (or never did). It is the first check after validateUserData, so an update that passes permission checks still fails here if the target was deleted. This is a plain Meteor.Error with code error-invalid-user and no extra details object.
Source
Thrown at apps/meteor/server/lib/users/saveUser/validateUserEditing.ts:41
return true;
};
/**
* Validate permissions to edit user fields
*
* @param {string} userId
* @param {{ _id: string, roles?: string[], username?: string, name?: string, statusText?: string, email?: string, password?: string}} userData
*/
export async function validateUserEditing(userId: IUser['_id'], userData: UpdateUserData): Promise<void> {
const editingMyself = userData._id && userId === userData._id;
const canEditOtherUserInfo = await hasPermissionAsync(userId, 'edit-other-user-info');
const canEditOtherUserPassword = await hasPermissionAsync(userId, 'edit-other-user-password');
const user = await Users.findOneById(userData._id);
if (!user) {
throw new MeteorError('error-invalid-user', 'Invalid user');
}
if (isEditingUserRoles(user.roles, userData.roles) && !(await hasPermissionAsync(userId, 'assign-roles'))) {
throw new MeteorError('error-action-not-allowed', 'Assign roles is not allowed', {
method: 'insertOrUpdateUser',
action: 'Assign_role',
});
}
if (!settings.get('Accounts_AllowUserProfileChange') && !canEditOtherUserInfo && !canEditOtherUserPassword) {
throw new MeteorError('error-action-not-allowed', 'Edit user profile is not allowed', {
method: 'insertOrUpdateUser',
action: 'Update_user',
});
}
if (
isEditingField(user.username, userData.username) &&View on GitHub (pinned to b2c16d5842)
Solutions
- Refresh the id: call GET /api/v1/users.info?userId=... and only update when the user is found.
- Re-list the source of truth (users.list) and reconcile before retrying the update.
- If the account was deleted intentionally, create it instead of updating, or stop treating it as existing in your sync state.
Example fix
// before
await POST '/api/v1/users.update', { userId: staleId, data: { name: 'x' } }); // throws 'Invalid user'
// after
const info = await GET `/api/v1/users.info?userId=${staleId}`;
if (info?.userinfo?._id) {
await POST '/api/v1/users.update', { userId: info.userinfo._id, data: { name: 'x' } });
} else {
await POST '/api/v1/users.create', { username: 'recreated', email: 'a@b.c', password: 'pw' });
} Defensive patterns
Strategy: validation
Validate before calling
const { userinfo } = await GET `/api/v1/users.info?userId=${encodeURIComponent(payload._id)}`;
if (!userinfo?._id) throw new Error('target user no longer exists'); Type guard
const isExistingUser = (u: { _id: string } | null | undefined): u is { _id: string } => !!u?._id; Try / catch
catch (e) {
if (e.error === 'error-invalid-user') {
// refresh the user list / reconcile sync state; do NOT retry with the same id
}
} Prevention
- Validate ids right before update, not from cached lists.
- Refresh sync state after deletes (webhooks or scheduled reconciliation).
- Scope stored ids per environment to avoid cross-env mistakes.
When it happens
Trigger: users.update for an _id returned by a stale list (user deleted between listing and submitting); a wrong/hand-typed _id; a federated or merged account whose id changed; delete-and-recreate race with another admin.
Common situations: Long-lived admin screens or external directory syncs holding cached ids; automation that stores user ids across restarts; ids copied from a different workspace/environment (e.g. staging id used against production).
Related errors
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/f0d03d41cdd468d7.
Report an issue: GitHub.