RocketChat/Rocket.Chat · error · Meteor.Error

error-room-e2e-key-already-exists

error-room-e2e-key-already-exists

Error message

E2E Key ID already exists

What it means

After the access check passes, `Rooms.setE2eKeyId(rid, keyID)` returning null makes the method throw `error-room-e2e-key-already-exists`: the model layer refuses to overwrite an existing `e2eKeyId`. Through this method a room's E2E key id is write-once; a second submission with a different key is rejected.

Source

Thrown at apps/meteor/server/meteor-methods/platform/setRoomKeyID.ts:25

import { canAccessRoomIdAsync } from '../../lib/authorization/canAccessRoom';
import { notifyOnRoomChanged } from '../../lib/notifyListener';

declare module '@rocket.chat/ddp-client' {
	// eslint-disable-next-line @typescript-eslint/naming-convention
	interface ServerMethods {
		'e2e.setRoomKeyID'(rid: IRoom['_id'], keyID: string): void;
	}
}

export const setRoomKeyIDMethod = async (userId: string, rid: IRoom['_id'], keyID: string): Promise<void> => {
	if (!(await canAccessRoomIdAsync(rid, userId))) {
		throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'e2e.setRoomKeyID' });
	}

	const room = await Rooms.setE2eKeyId(rid, keyID);

	if (!room) {
		throw new Meteor.Error('error-room-e2e-key-already-exists', 'E2E Key ID already exists', {
			method: 'e2e.setRoomKeyID',
		});
	}

	void notifyOnRoomChanged(room);
};

Meteor.methods<ServerMethods>({
	async 'e2e.setRoomKeyID'(rid, keyID) {
		check(rid, String);
		check(keyID, String);

		const userId = Meteor.userId();
		if (!userId) {
			throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'e2e.setRoomKeyID' });
		}

		if (!rid) {

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Read the room's `e2eKeyId` (rooms subscription) first and skip the call when it is already set
  2. Make the client's key submission idempotent (guard with a pending/submitted flag)
  3. For genuine key rotation, use the designated reset flow (e.g. `e2e.resetOwnE2EKey` / admin reset) instead of calling setRoomKeyID again

Example fix

// before
Meteor.call('e2e.setRoomKeyID', rid, keyID);
// after
const room = Rooms.findOne({ _id: rid });
if (room?.e2eKeyId) {
  return; // key already established, nothing to do
}
Meteor.call('e2e.setRoomKeyID', rid, keyID);
Defensive patterns

Strategy: validation

Validate before calling

const room = Rooms.findOne({ _id: rid });
if (room?.e2eKeyId) {
  return; // already established — skip the call entirely
}
Meteor.call('e2e.setRoomKeyID', rid, keyID);

Type guard

const isRoomKeyUnset = (room: IRoom | undefined | null): room is IRoom =>
  !!room && !room.e2eKeyId;

Try / catch

try {
  await Meteor.callAsync('e2e.setRoomKeyID', rid, keyID);
} catch (e) {
  if (e instanceof Meteor.Error && e.error === 'error-room-e2e-key-already-exists') {
    // treat as success (key already set) or re-read the room key and adopt it
  }
}

Prevention

When it happens

Trigger: Calling `e2e.setRoomKeyID` twice for the same room — duplicate submission (double click), a retry after a timeout where the first call actually committed, or an attempted key rotation without going through a reset flow.

Common situations: Non-idempotent E2E setup wizards re-running on reconnect; retry logic resending the key; two clients racing to set the room key; network slowness making the user resubmit.

Related errors


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