RocketChat/Rocket.Chat · error · Meteor.Error
error-direct-message-room
error-direct-message-room
Error message
rooms type: ${room.t} can not be archived What it means
Thrown when the room exists but its type coordinator refuses the ARCHIVE member action: roomCoordinator.getRoomDirectives(room.t).allowMemberAction(room, RoomMemberActions.ARCHIVE, userId) returned false. Despite the legacy code error-direct-message-room, this guards every room type, not only direct messages - any type whose directives do not allow archiving for this user lands here.
Source
Thrown at apps/meteor/server/meteor-methods/rooms/archiveRoom.ts:34
archiveRoom(rid: string): Promise<void>;
}
}
export const executeArchiveRoom = async (userId: string, rid: string) => {
check(rid, String);
const user = await Users.findOneById(userId, { projection: { username: 1, name: 1 } });
if (!user || !isRegisterUser(user)) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'archiveRoom' });
}
const room = await Rooms.findOneById(rid);
if (!room) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'archiveRoom' });
}
if (!(await roomCoordinator.getRoomDirectives(room.t).allowMemberAction(room, RoomMemberActions.ARCHIVE, userId))) {
throw new Meteor.Error('error-direct-message-room', `rooms type: ${room.t} can not be archived`, { method: 'archiveRoom' });
}
if (!(await hasPermissionAsync(userId, 'archive-room', room._id))) {
throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'archiveRoom' });
}
return archiveRoom(rid, user);
};
Meteor.methods<ServerMethods>({
async archiveRoom(rid) {
methodDeprecationLogger.method('archiveRoom', '9.0.0', '/v1/channels.archive');
const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'archiveRoom' });
}
return executeArchiveRoom(userId, rid);View on GitHub (pinned to b2c16d5842)
Solutions
- Filter the archive action by room type before invoking: stock archivable types are 'c' (channel) and 'p' (private group).
- For direct messages, use hide/close conversation instead of archive.
- If you maintain a custom room type, implement allowMemberAction for RoomMemberActions.ARCHIVE in its coordinator.
Example fix
// before
Meteor.call('archiveRoom', room._id); // fires for DMs too
// after
const isArchivable = (t: string) => t === 'c' || t === 'p';
if (isArchivable(room.t)) {
Meteor.call('archiveRoom', room._id);
} else {
Meteor.call('hideRoom', room._id);
} Defensive patterns
Strategy: type-guard
Validate before calling
const room = roomsCollection.findOne({ _id: rid }, { fields: { t: 1 } });
if (room && !isArchivableRoom(room)) {
// this room type cannot be archived - offer hide/close instead
} Type guard
const isArchivableRoom = (room: { t: string }): room is { t: 'c' | 'p' } =>
room.t === 'c' || room.t === 'p'; Try / catch
try {
await Meteor.callAsync('archiveRoom', rid);
} catch (e) {
if (e instanceof Meteor.Error && e.error === 'error-direct-message-room') {
// room type does not support archive - hide the conversation instead
await Meteor.callAsync('hideRoom', rid);
return;
}
throw e;
} Prevention
- Gate the archive action on room type (only 'c'/'p' for stock types).
- Remember the legacy error code covers every non-archivable type, not just direct messages.
- Keep custom room-type directives in sync with the client capability checks that reveal the archive action.
When it happens
Trigger: Archiving a direct-message room (t='d'); archiving room types whose directives do not register the ARCHIVE member action for the caller; custom room types (apps/forks) whose coordinator omits allowMemberAction for ARCHIVE.
Common situations: UIs that enable archive for every listed room including DMs; scripts walking all subscriptions and archiving every rid; custom room types added without archive support.
Related errors
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/14f204e8fa5f8e67.
Report an issue: GitHub.