RocketChat/Rocket.Chat · error · Meteor.Error

error-encrypted-private-rooms-enforced-discussion

error-encrypted-private-rooms-enforced-discussion

Error message

Workspace policy requires all private rooms to be encrypted. To create this discussion, make the parent channel public or enable encryption on it.

What it means

Thrown by the createDiscussion server flow when the resolved discussion type is private ('p', inherited from a private parent channel via roomCoordinator.getDiscussionType), the effective encrypted flag is false, and the workspace enforces E2E_Enable plus E2E_Force_Encryption_For_Private_Rooms. Discussions inherit encryption from the parent room (createDiscussion.ts:116-118 defaults encrypted to Boolean(parentRoom.encrypted)), so an unencrypted private parent would produce an unencrypted private discussion, which the policy forbids. Passing encrypted: true explicitly bypasses this check, but then the call must omit reply, because encrypted discussions reject initial replies (createDiscussion.ts:120 throws error-invalid-arguments).

Source

Thrown at apps/meteor/server/meteor-methods/messages/createDiscussion.ts:160

	const invitedUsers = message ? [message.u.username, ...users] : users;

	const type = await roomCoordinator.getRoomDirectives(parentRoom.t).getDiscussionType(parentRoom);
	const description = parentRoom.encrypted ? '' : message?.msg;
	const discussionTopic = topic || parentRoom.name;

	if (!type) {
		throw new Meteor.Error('error-invalid-type', 'Cannot define discussion room type', {
			method: 'DiscussionCreation',
		});
	}

	if (
		type === 'p' &&
		!encrypted &&
		settings.get<boolean>('E2E_Enable') &&
		settings.get<boolean>('E2E_Force_Encryption_For_Private_Rooms')
	) {
		throw new Meteor.Error(
			'error-encrypted-private-rooms-enforced-discussion',
			'Workspace policy requires all private rooms to be encrypted. To create this discussion, make the parent channel public or enable encryption on it.',
			{ method: 'DiscussionCreation' },
		);
	}

	const discussion = await createRoom(
		type,
		name,
		user,
		[...new Set(invitedUsers)].filter(Boolean),
		false,
		false,
		{
			fname: discussionName,
			description, // TODO discussions remove
			topic: discussionTopic,
			prid,

View on GitHub (pinned to 2a7de45707)

Solutions

  1. Enable E2E encryption on the parent private channel first (room header → Encryption), then retry — the discussion inherits encrypted=true and passes.
  2. Pass encrypted: true in the createDiscussion call so the discussion itself is E2E even though the parent is not; you must also omit the reply field, since encrypted discussions cannot take an initial reply.
  3. Make the parent channel public (type 'c') and retry — public rooms sit outside the force-encryption policy.
  4. Or have a workspace admin disable E2E_Force_Encryption_For_Private_Rooms if unencrypted private discussions must remain possible.

Example fix

// before — parent room is private + unencrypted, policy enabled → throws
// error-encrypted-private-rooms-enforced-discussion
await Meteor.callAsync('createDiscussion', {
  prid: 'parentRoomId', t_name: 'Sprint sync', users: ['alice'], reply: 'kickoff',
});

// after — opt the discussion into E2E explicitly and drop the initial reply
await Meteor.callAsync('createDiscussion', {
  prid: 'parentRoomId', t_name: 'Sprint sync', users: ['alice'], encrypted: true,
});
Defensive patterns

Strategy: validation

Validate before calling

import { Rooms } from '@rocket.chat/models';
import { settings } from '../../settings';

const privateDiscussionBlockedByPolicy = async (
  prid: string,
  encrypted?: boolean,
): Promise<boolean> => {
  const parent = await Rooms.findOneById(prid, { projection: { t: 1, encrypted: 1 } });
  if (!parent || parent.t !== 'p') return false; // only private parents get a 'p' discussion
  const effectiveEncrypted = typeof encrypted === 'boolean' ? encrypted : Boolean(parent.encrypted);
  return (
    !effectiveEncrypted &&
    settings.get<boolean>('E2E_Enable') === true &&
    settings.get<boolean>('E2E_Force_Encryption_For_Private_Rooms') === true
  );
};

if (await privateDiscussionBlockedByPolicy(prid, encrypted)) {
  // encrypt the parent, pass encrypted: true (with no reply), or block the action in the UI
}

Try / catch

try {
  await Meteor.callAsync('createDiscussion', { prid, t_name, users });
} catch (error) {
  if (error instanceof Meteor.Error && error.error === 'error-encrypted-private-rooms-enforced-discussion') {
    // offer the user the two policy-compliant paths: encrypt the parent channel,
    // or retry with encrypted: true (and no initial reply)
    return Meteor.callAsync('createDiscussion', { prid, t_name, users, encrypted: true });
  }
  throw error;
}

Prevention

When it happens

Trigger: Meteor method createDiscussion (deprecated in 9.0.0 in favor of POST /v1/rooms.createDiscussion) with prid/pmid pointing at a private, unencrypted channel while E2E_Enable and E2E_Force_Encryption_For_Private_Rooms are both true — either with no encrypted argument (inherits false from the parent) or with an explicit encrypted:false. The check fires after type resolution and after the reply/encrypted mutual-exclusion check.

Common situations: An admin flips on forced encryption on an existing workspace where users already have private unencrypted channels — every 'Create discussion' action on those channels now fails until parents are encrypted. Also hit by REST integrations that never send the encrypted field for private parents, or when replaying a createDiscussion payload recorded on a non-E2E workspace against an E2E-enforcing one.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@2a7de45707 (2026-08-21). Data as JSON: /api/errors/5808b86032b2060d. Report an issue: GitHub.