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

  1. Add the user (or integration user) to the room: invite them, or channels.join for public channels
  2. For integrations, grant membership first or post as a bot that was explicitly added to the channel
  3. 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

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


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