RocketChat/Rocket.Chat · error · 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
Thrown by Rocket.Chat's setReaction flow when a user tries to add or remove a reaction in a read-only room. A room with ro === true blocks reactions unless reactWhenReadOnly is also true, the user holds the 'post-readonly' permission in that room, or the user was manually added to the room's unmuted list. It is a plain JavaScript Error (not a Meteor.Error), so it surfaces to DDP clients as a generic error carrying only the message text.
Source
Thrown at apps/meteor/server/lib/messaging/reactions/setReaction.ts:46
if (!message.reactions[reaction].usernames.length) {
delete message.reactions[reaction];
}
return message;
};
export async function setReaction(room: IRoom, user: IUser, message: IMessage, reaction: string, userAlreadyReacted?: boolean) {
await Message.beforeReacted(message, room);
if (Array.isArray(room.muted) && room.muted.includes(user.username as string)) {
throw new Meteor.Error('error-not-allowed', i18n.t('You_have_been_muted', { lng: user.language }), {
rid: room._id,
});
}
if (room.ro === true && !room.reactWhenReadOnly && !(await hasPermissionAsync(user, 'post-readonly', room._id))) {
// Unless the user was manually unmuted
if (!(room.unmuted || []).includes(user.username as string)) {
throw new Error("You can't send messages because the room is readonly.");
}
}
let isReacted;
if (userAlreadyReacted) {
const oldMessage = JSON.parse(JSON.stringify(message));
removeUserReaction(message, reaction, user.username as string);
if (Object.keys(message.reactions || {}).length === 0) {
delete message.reactions;
await Messages.unsetReactions(message._id);
if (isTheLastMessage(room, message)) {
await Rooms.unsetReactionsInLastMessage(room._id);
}
} else {
await Messages.setReactions(message._id, message.reactions);
if (isTheLastMessage(room, message)) {
await Rooms.setReactionsInLastMessage(room._id, message.reactions);
}View on GitHub (pinned to b2c16d5842)
Solutions
- Enable 'React When Read Only' on the room so members can react while it stays read-only
- Grant the caller's role the 'post-readonly' permission (globally or scoped to that room)
- Manually unmute the specific user by adding their username to the room's unmuted list
- Take the room out of read-only mode if reactions should follow normal posting rights
- Client-side: check room.ro / reactWhenReadOnly / unmuted before rendering the reaction picker
Example fix
// before
Meteor.call('setReaction', 'tada', messageId);
// after - guard on room flags you already have from the room subscription
const room = Rooms.findOne({ _id: rid });
const canReact = !room?.ro || room?.reactWhenReadOnly || room?.unmuted?.includes(user.username);
if (canReact) {
Meteor.call('setReaction', 'tada', messageId);
} else {
toast.warn(i18n.t('Room_is_read_only')); Defensive patterns
Strategy: validation
Validate before calling
// Server-side pre-check mirroring the method's own gate
const allowed =
room.ro !== true ||
room.reactWhenReadOnly === true ||
(room.unmuted || []).includes(user.username as string) ||
(await hasPermissionAsync(user, 'post-readonly', room._id));
if (allowed) {
await setReaction(room, user, message, reaction, userAlreadyReacted);
} Try / catch
try {
Meteor.call('setReaction', 'tada', messageId);
} catch (e) {
if (e instanceof Error && e.message.includes('readonly')) {
// permission state, not transient - inform the user, do not retry
} else {
throw e;
}
} Prevention
- Sync room flags (ro, reactWhenReadOnly) via the rooms stream before enabling reaction UI
- Hide the reaction picker in read-only rooms unless the user is known to have post-readonly
- Re-fetch room state when the room is switched to read-only instead of trusting stale cache
When it happens
Trigger: DDP call Meteor.call('setReaction', emoji, messageId), the REST/apps path, or a direct setReaction() call where room.ro === true, room.reactWhenReadOnly is falsy, hasPermissionAsync(user, 'post-readonly', room._id) fails, and user.username is not in room.unmuted. Also thrown when un-reacting in such a room.
Common situations: Announcement/broadcast channels toggled read-only by admins; archived rooms that force ro = true; bots or apps trying to react without the post-readonly role; users with the room still open from before the read-only change.
Related errors
- You can't delete messages because the room is readonly.
- You can't send messages because the room is readonly.
- error-invalid-user
- not-authorized
- The setting "${id}" is not readable.
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/e43595d5845135a0.
Report an issue: GitHub.