RocketChat/Rocket.Chat · error
error-not-allowed
Error message
error-not-allowed
What it means
validateRoomMessagePermissionsAsync throws error-not-allowed (plain Error) when the acting user (type !== 'app') fails canAccessRoomAsync(room, user, extraData) — the standard room access check: private channels and teams require membership, while public rooms are open. App users skip this check entirely.
Source
Thrown at apps/meteor/server/lib/authorization/canSendMessage.ts:30
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');
}
}
if (room.ro === true && !(await hasPermissionAsync('uid' in args ? args.uid : args._id, 'post-readonly', room._id))) {
// Unless the user was manually unmuted
if (args.username && !(room.unmuted || []).includes(args.username)) {
throw new Error("You can't send messages because the room is readonly.");
}
}
View on GitHub (pinned to b2c16d5842)
Solutions
- Add the user (or integration user) to the room: invite them, or channels.join for public channels
- For integrations, grant membership first or post as a bot that was explicitly added to the channel
- Derive valid rids from the caller's subscriptions instead of hardcoding them
Defensive patterns
Strategy: try-catch
Validate before calling
const membership = await Subscriptions.findOneByRoomIdAndUserId(room._id, uid, { projection: { _id: 1 } });
if (room.t === 'p' && !membership) {
// private room and not a member: join/invite first instead of sending
} Try / catch
try {
await sendMessage({ rid, ... });
} catch (e) {
if (e instanceof Error && e.message === 'error-not-allowed') {
// access denied for this room: request membership/invite; do not retry with same credentials
}
throw e;
} Prevention
- Add integration users to private rooms explicitly before configuring them
- Fetch valid rids from the acting user's subscriptions
- Re-check membership after users are removed from teams/channels
When it happens
Trigger: Sending to a private channel, team room, or DM the user is not a member of — chat.postMessage with a rid the account never joined, or after being removed from the room.
Common situations: Integrations posting to private channels without being invited; REST scripts reusing a rid after the account lost membership; race where the user is kicked mid-session.
Related errors
- error-not-authorized
- error-not-allowed
- error-not-allowed
- The required "roomId" or "roomName" param provided does not
- error-invalid-user
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/0d51eb28010b4061.
Report an issue: GitHub.