{"record":{"id":"e43595d5845135a0","repo":"RocketChat/Rocket.Chat","slug":"you-can-t-send-messages-because-the-room-is-readon-e43595","errorCode":null,"errorMessage":"You can't send messages because the room is readonly.","messagePattern":"You can't send messages because the room is readonly\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/lib/messaging/reactions/setReaction.ts","lineNumber":46,"sourceCode":"\tif (!message.reactions[reaction].usernames.length) {\n\t\tdelete message.reactions[reaction];\n\t}\n\treturn message;\n};\n\nexport async function setReaction(room: IRoom, user: IUser, message: IMessage, reaction: string, userAlreadyReacted?: boolean) {\n\tawait Message.beforeReacted(message, room);\n\n\tif (Array.isArray(room.muted) && room.muted.includes(user.username as string)) {\n\t\tthrow new Meteor.Error('error-not-allowed', i18n.t('You_have_been_muted', { lng: user.language }), {\n\t\t\trid: room._id,\n\t\t});\n\t}\n\n\tif (room.ro === true && !room.reactWhenReadOnly && !(await hasPermissionAsync(user, 'post-readonly', room._id))) {\n\t\t// Unless the user was manually unmuted\n\t\tif (!(room.unmuted || []).includes(user.username as string)) {\n\t\t\tthrow new Error(\"You can't send messages because the room is readonly.\");\n\t\t}\n\t}\n\n\tlet isReacted;\n\tif (userAlreadyReacted) {\n\t\tconst oldMessage = JSON.parse(JSON.stringify(message));\n\t\tremoveUserReaction(message, reaction, user.username as string);\n\t\tif (Object.keys(message.reactions || {}).length === 0) {\n\t\t\tdelete message.reactions;\n\t\t\tawait Messages.unsetReactions(message._id);\n\t\t\tif (isTheLastMessage(room, message)) {\n\t\t\t\tawait Rooms.unsetReactionsInLastMessage(room._id);\n\t\t\t}\n\t\t} else {\n\t\t\tawait Messages.setReactions(message._id, message.reactions);\n\t\t\tif (isTheLastMessage(room, message)) {\n\t\t\t\tawait Rooms.setReactionsInLastMessage(room._id, message.reactions);\n\t\t\t}","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/lib/messaging/reactions/setReaction.ts#L28-L64","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nMeteor.call('setReaction', 'tada', messageId);\n\n// after - guard on room flags you already have from the room subscription\nconst room = Rooms.findOne({ _id: rid });\nconst canReact = !room?.ro || room?.reactWhenReadOnly || room?.unmuted?.includes(user.username);\nif (canReact) {\n  Meteor.call('setReaction', 'tada', messageId);\n} else {\n  toast.warn(i18n.t('Room_is_read_only'));","handlingStrategy":"validation","validationCode":"// Server-side pre-check mirroring the method's own gate\nconst allowed =\n  room.ro !== true ||\n  room.reactWhenReadOnly === true ||\n  (room.unmuted || []).includes(user.username as string) ||\n  (await hasPermissionAsync(user, 'post-readonly', room._id));\nif (allowed) {\n  await setReaction(room, user, message, reaction, userAlreadyReacted);\n}","typeGuard":null,"tryCatchPattern":"try {\n  Meteor.call('setReaction', 'tada', messageId);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('readonly')) {\n    // permission state, not transient - inform the user, do not retry\n  } else {\n    throw e;\n  }\n}","preventionTips":["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"],"tags":["reactions","readonly-room","permissions","ddp-method"],"backgroundTag":"readonly-room-permission-denied","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}