RocketChat/Rocket.Chat · error · Meteor.Error

error-cannot-delete-app-user

error-cannot-delete-app-user

Error message

Deleting app user is not allowed

What it means

`executeDeleteUser` refuses to delete users whose `type === 'app'`: accounts created and owned by the Apps Engine to run apps. These service users are protected because deleting them corrupts the associated app; the correct lifecycle path is uninstalling the app, which removes its user. The check runs right after the existence check and before the last-admin guard.

Source

Thrown at apps/meteor/server/meteor-methods/users/deleteUser.ts:27

import { deleteUser } from '../../lib/users/deleteUser';

declare module '@rocket.chat/ddp-client' {
	// eslint-disable-next-line @typescript-eslint/naming-convention
	interface ServerMethods {
		deleteUser(userId: IUser['_id'], confirmRelinquish?: boolean): boolean;
	}
}

export const executeDeleteUser = async (fromUserId: IUser['_id'], userId: IUser['_id'], confirmRelinquish = false): Promise<boolean> => {
	const user = await Users.findOneById(userId);
	if (!user) {
		throw new Meteor.Error('error-invalid-user', 'Invalid user to delete', {
			method: 'deleteUser',
		});
	}

	if (user.type === 'app') {
		throw new Meteor.Error('error-cannot-delete-app-user', 'Deleting app user is not allowed', {
			method: 'deleteUser',
		});
	}

	const adminCount = await Users.countDocuments({ roles: 'admin' });

	const userIsAdmin = user.roles?.indexOf('admin') > -1;

	if (adminCount === 1 && userIsAdmin) {
		throw new Meteor.Error('error-action-not-allowed', 'Leaving the app without admins is not allowed', {
			method: 'deleteUser',
			action: 'Remove_last_admin',
		});
	}

	await deleteUser(userId, confirmRelinquish, fromUserId);

	return true;

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Uninstall the owning app (Admin → Apps) instead of deleting its user — the app lifecycle removes the service account.
  2. In bulk-delete scripts, filter targets with `type !== 'app'` before calling deleteUser.
  3. If an app user is orphaned (app already gone), remove it via the apps management path or DB cleanup with the app records together — not via this method.
  4. Never delete app users directly from the database while the app remains installed.
Defensive patterns

Strategy: type-guard

Validate before calling

const user = await Users.findOneById(userId, { projection: { type: 1 } });
if (user && user.type !== 'app') {
  await deleteUserFn(uid, userId);
}

Type guard

const isDeletableUserType = (user: { type?: string } | null): boolean => !!user && user.type !== 'app';

Try / catch

try {
  await Meteor.callAsync('deleteUser', userId);
} catch (e: any) {
  if (e?.error === 'error-cannot-delete-app-user') {
    // route to app uninstall instead of user deletion
  }
}

Prevention

When it happens

Trigger: Calling `deleteUser` (or REST `DELETE /v1/users.delete`) targeting an app user — typically visible in user lists as the app's bot/service account (`type: 'app'` in the users collection).

Common situations: Admins trying to clean up 'strange' bot users created by installed apps; scripts that bulk-delete users without filtering by type; app users lingering in lists after a failed app installation, tempting manual removal.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/ada8c126e9888879. Report an issue: GitHub.