RocketChat/Rocket.Chat · error · Meteor.Error
error-app-prevented
error-app-prevented
Error message
error.message
What it means
Thrown by 'removeUserFromRoom' when an installed Rocket.Chat App intercepts the IPreRoomUserLeave lifecycle event and throws an AppsEngineException to veto the removal. Apps can run pre-hooks before any user is kicked from a channel/group; a veto aborts the whole operation before the subscription is deleted. The thrown Meteor.Error carries the app's own message (error.message), so the reason text comes from the app, not Rocket.Chat core.
Source
Thrown at apps/meteor/server/meteor-methods/rooms/removeUserFromRoom.ts:92
});
}
}
if (await hasRoleAsync(removedUser._id, 'owner', room._id)) {
const numOwners = await Roles.countUsersInRole('owner', room._id);
if (numOwners === 1) {
throw new Meteor.Error('error-you-are-last-owner', 'You are the last owner. Please set new owner before leaving the room.', {
method: 'removeUserFromRoom',
});
}
}
try {
await Apps.self?.triggerEvent(AppEvents.IPreRoomUserLeave, room, removedUser, fromUser);
} catch (error: any) {
if (error.name === AppsEngineException.name) {
throw new Meteor.Error('error-app-prevented', error.message);
}
throw error;
}
await callbacks.run('beforeRemoveFromRoom', { removedUser, userWhoRemoved: fromUser }, room);
const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(data.rid, removedUser._id);
if (deletedSubscription) {
void notifyOnSubscriptionChanged(deletedSubscription, 'removed');
}
if (['c', 'p'].includes(room.t) === true) {
await removeUserFromRolesAsync(removedUser._id, ['moderator', 'owner'], data.rid);
}
await Message.saveSystemMessage('ru', data.rid, removedUser.username || '', fromUser);
View on GitHub (pinned to b2c16d5842)
Solutions
- Read the error message: it is the app's explanation for the veto; identify which installed app listens to IPreRoomUserLeave (Apps admin screen, or check each app's source for the event).
- Adjust or disable the app's rule (or update its configuration) so the kick is permitted.
- Temporarily disable the app if the removal is operationally required, kick the user, then re-enable.
- If you develop the app, only throw AppsEngineException when truly necessary and return a descriptive message.
Example fix
// app handler causing it (apps-engine)
// before
this.getLogger().log('keeping user');
throw new AppsEngineException('no reason given'); // cryptic veto
// after
throw new AppsEngineException(`User ${user.username} is required in ${room.name} by SyncApp; remove mapping first.`); Defensive patterns
Strategy: try-catch
Validate before calling
// Discover apps that can veto room-user-leave before rolling out kick automation
const apps = await (await fetch('/api/v1/apps?marketplace=false', { headers: adminHeaders })).json();
const vetoCapable = apps.apps.filter((a) => a.status === 'manual_enabled' && implementsIPreRoomUserLeave(a));
// check each app's source/changelog for AppEvents.IPreRoomUserLeave handlers Try / catch
try {
await Meteor.callAsync('removeUserFromRoom', { rid, username });
} catch (err) {
if (err instanceof Meteor.Error && err.error === 'error-app-prevented') {
// err.message comes from the app; surface it to the operator, never retry blindly
throw new Error(`An app blocked this removal: ${err.message}`);
}
throw err;
} Prevention
- Audit installed apps for IPreRoomUserLeave handlers before building membership automation.
- Keep veto reasons actionable: app authors should include who/why in the exception message.
- For emergency removals, disable the vetoing app first, kick, then re-enable.
- Never auto-retry this error — the app will keep vetoing.
When it happens
Trigger: A governance/compliance app blocking removal of users it tracks; an app enforcing retention of team members; any app registered on AppEvents.IPreRoomUserLeave that calls preventDefault/throws for this room-user pair. Triggered by both the DDP method and the REST channels.kick/groups.kick endpoints that reuse this logic.
Common situations: Enterprise workspaces with custom apps managing membership sync; marketplace apps that pin membership (e.g. SAML/LDAP-synced teams); an app upgraded with stricter rules that now vetoes kicks it previously allowed; locally developed app throwing AppsEngineException unintentionally inside the handler.
Related errors
- error-invalid-command
- error-invalid-command
- error-user-not-in-room
- error-you-are-last-owner
- Failed to get apps status from node ${nodeID}
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/90225b03422df4c0.
Report an issue: GitHub.