RocketChat/Rocket.Chat · error · Meteor.Error
error-app-prevented-deleting
error-app-prevented-deleting
Error message
A Rocket.Chat App prevented the message deleting.
What it means
deleteMessage (deleteMessage.ts:38-44) fires the Apps Engine IPreMessageDeletePrevent event before deleting. If an installed app returns a truthy 'prevent' result, the server aborts with error-app-prevented-deleting 'A Rocket.Chat App prevented the message deleting.' This is deliberate app-side policy (e.g. retention/legal hold, moderation apps), not a bug.
Source
Thrown at apps/meteor/server/lib/messages/deleteMessage.ts:42
if (!originalMessage || !user || !(await canDeleteMessageAsync(user, originalMessage))) {
throw new Meteor.Error('error-action-not-allowed', 'Not allowed');
}
return deleteMessage(originalMessage, user);
};
export async function deleteMessage(message: IMessage, user: IUser): Promise<void> {
const deletedMsg: IMessage | null = await Messages.findOneById(message._id);
const isThread = (deletedMsg?.tcount || 0) > 0;
const keepHistory = settings.get('Message_KeepHistory') || isThread;
const showDeletedStatus = settings.get('Message_ShowDeletedStatus') || isThread;
const room = await Rooms.findOneById(message.rid, { projection: { lastMessage: 1, prid: 1, mid: 1, federated: 1, federation: 1 } });
if (deletedMsg) {
const prevent = await Apps.self?.triggerEvent(AppEvents.IPreMessageDeletePrevent, deletedMsg);
if (prevent) {
throw new Meteor.Error('error-app-prevented-deleting', 'A Rocket.Chat App prevented the message deleting.');
}
if (room) {
await Message.beforeDelete(deletedMsg, room);
}
}
if (deletedMsg && isThreadMessage(deletedMsg)) {
await deleteThreadMessage(deletedMsg, user, room);
}
const files = (message.files || [message.file]).filter(Boolean); // Keep compatibility with old messages
if (keepHistory) {
if (showDeletedStatus) {
// TODO is there a better way to tell TS "IUser[username]" is not undefined?
await Messages.cloneAndSaveAsHistoryById(message._id, user as Required<Pick<IUser, '_id' | 'username' | 'name'>>);
} else {View on GitHub (pinned to b2c16d5842)
Solutions
- Check Administration > Apps for apps registering IPreMessageDeletePrevent and read their rules/logs to see why this message is protected
- Delete via the app's own escape hatch if it provides one (e.g. a moderator command), or remove/relax the app policy
- If the prevention is unintended, disable the app and retry the deletion
- If you author the app, only return true deliberately and log the reason alongside
Defensive patterns
Strategy: try-catch
Try / catch
try {
await deleteMessage(message, user);
} catch (error: any) {
if (error instanceof Meteor.Error && error.error === 'error-app-prevented-deleting') {
showAppProtectedMessage(); // an App holds this message; do not retry
return;
}
throw error;
} Prevention
- Know which installed apps register IPreMessageDeletePrevent
- Give users a UI hint when a retention/hold app protects messages
- Do not auto-retry this error - it is a deliberate block
When it happens
Trigger: An app implementing IPreMessageDeletePrevent (retention hold, audit, anti-abuse) returning true for the message being deleted; deleting messages inside a room covered by such an app's rules; app regression causing it to prevent all deletions.
Common situations: Compliance installs with retention apps holding messages; moderation apps blocking deletes of flagged content; admins unaware an installed app intercepts deletes; app authors accidentally returning truthy values from the hook.
Related errors
- error-app-prevented
- Message converter not found
- error-invalid-actionlink
- error-invalid-message
- error-invalid-user
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/b11f7973f6647c9d.
Report an issue: GitHub.