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

  1. Enable 'React When Read Only' on the room so members can react while it stays read-only
  2. Grant the caller's role the 'post-readonly' permission (globally or scoped to that room)
  3. Manually unmute the specific user by adding their username to the room's unmuted list
  4. Take the room out of read-only mode if reactions should follow normal posting rights
  5. 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

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


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/e43595d5845135a0. Report an issue: GitHub.