RocketChat/Rocket.Chat · error
error-invalid-room
Error message
error-invalid-room
What it means
validateRoomMessagePermissionsAsync throws error-invalid-room (plain Error) when the room argument passed in is null. This differs from the lookup variant in canSendMessageAsync: here the caller already resolved the room and passed null, so the failure is an upstream lookup that found nothing.
Source
Thrown at apps/meteor/server/lib/authorization/canSendMessage.ts:23
import { hasPermissionAsync } from './hasPermission';
import { RoomMemberActions } from '../../../definition/IRoomTypeConfig';
import { roomCoordinator } from '../rooms/roomCoordinator';
const subscriptionOptions = {
projection: {
blocked: 1,
blocker: 1,
},
};
// TODO: remove option uid and username and type
export async function validateRoomMessagePermissionsAsync(
room: IRoom | null,
args: { uid: IUser['_id']; username: IUser['username']; type: IUser['type'] } | IUser,
extraData?: Record<string, any>,
): Promise<void> {
if (!room) {
throw new Error('error-invalid-room');
}
if (room.archived) {
throw new Error('room_is_archived');
}
if (args.type !== 'app' && !(await canAccessRoomAsync(room, 'uid' in args ? { _id: args.uid } : args, extraData))) {
throw new Error('error-not-allowed');
}
if (
await roomCoordinator.getRoomDirectives(room.t).allowMemberAction(room, RoomMemberActions.BLOCK, 'uid' in args ? args.uid : args._id)
) {
const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, 'uid' in args ? args.uid : args._id, subscriptionOptions);
if (subscription && (subscription.blocked || subscription.blocker)) {
throw new Error('room_is_blocked');
}
}
View on GitHub (pinned to b2c16d5842)
Solutions
- Check the room lookup result before calling and fail fast with a clear error if missing
- Prefer canSendMessageAsync(rid, user) which performs lookup plus validation in one step
- Validate rid existence at the trust boundary (API handler/webhook) before touching authorization
Example fix
// before
const room = await Rooms.findOneById(rid);
await validateRoomMessagePermissionsAsync(room, user); // room may be null -> error-invalid-room
// after
const room = await Rooms.findOneById(rid);
if (!room) throw new Meteor.Error('error-invalid-room', `Room ${rid} not found`);
await validateRoomMessagePermissionsAsync(room, user); Defensive patterns
Strategy: type-guard
Validate before calling
const room = await Rooms.findOneById(rid);
if (!isExistingRoom(room)) {
throw new Meteor.Error('error-invalid-room', `Room not found: ${rid}`);
}
await validateRoomMessagePermissionsAsync(room, user, extraData); Type guard
const isExistingRoom = (room: IRoom | null | undefined): room is IRoom => !!room?._id;
Prevention
- Never call permission validators with unchecked lookup results
- Centralize room resolution in one helper that throws a descriptive error
- Validate rid inputs at API/webhook boundaries
When it happens
Trigger: Calling validateRoomMessagePermissionsAsync(null, user, ...) — code that did Rooms.findOneById(rid) without checking the result, or a hook/webhook passing an absent room object.
Common situations: Integrations forwarding to rooms that were deleted; stale or mistyped room ids in configuration; code refactored to call the permissions function before validating its own lookup.
Related errors
- Only channels, private groups and direct messages can be cre
- Invalid user
- error-invalid-room
- error-not-authorized
- error-not-allowed
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/26e4dc1387309e35.
Report an issue: GitHub.