RocketChat/Rocket.Chat · error
You can't send messages because the room is readonly.
Error message
You can't send messages because the room is readonly.
What it means
The send-path readonly guard: validateRoomMessagePermissionsAsync throws this plain Error when room.ro === true, the user lacks the 'post-readonly' permission in that room, and their username is not in room.unmuted. Read-only rooms accept posts only from unmuted users or holders of post-readonly (typically owners and moderators).
Source
Thrown at apps/meteor/server/lib/authorization/canSendMessage.ts:45
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.");
}
}
if (args.username && room?.muted?.includes(args.username)) {
throw new Error('You_have_been_muted');
}
}
// TODO: remove option uid and username and type
export async function canSendMessageAsync(
rid: IRoom['_id'],
user: { uid: IUser['_id']; username: IUser['username']; type: IUser['type'] } | IUser,
extraData?: Record<string, any>,
): Promise<IRoom> {
const room = await Rooms.findOneById(rid);
if (!room) {
throw new Error('error-invalid-room');
}
View on GitHub (pinned to b2c16d5842)
Solutions
- Grant 'post-readonly' to the role that must speak in read-only rooms (Administration -> Permissions)
- Unmute the specific users in the read-only room (adds them to room.unmuted)
- Turn read-only off when the restriction is no longer needed
- In clients, disable the composer when room.ro is set for users without post-readonly, so the error never fires
Defensive patterns
Strategy: validation
Validate before calling
const canSpeak =
room.ro !== true ||
(await hasPermissionAsync(uid, 'post-readonly', room._id)) ||
(room.unmuted || []).includes(username);
if (!canSpeak) {
// disable the composer for this room instead of letting the send fail
} Try / catch
try {
await sendMessage(...);
} catch (e) {
if (e instanceof Error && e.message.includes('room is readonly')) {
// permanent state: inform the user the channel is read-only
}
throw e;
} Prevention
- Disable the composer client-side when room.ro is set without post-readonly
- When toggling read-only during incidents, unmute responders at the same time
- Keep role grants of post-readonly aligned with moderation policy
When it happens
Trigger: chat.sendMessage / chat.postMessage / UI send in a read-only channel as a regular member who has not been unmuted by a moderator.
Common situations: Announcement channels that are read-only by design; read-only toggled on during an incident while responders were never unmuted; default roles lacking post-readonly.
Related errors
- You can't delete messages because the room is readonly.
- error-action-not-allowed
- Not_authorized
- error-not-authorized-federation
- error-not-authorized
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/bf147d1dffa00b58.
Report an issue: GitHub.