RocketChat/Rocket.Chat · error · MeteorError

error-extension-not-available

error-extension-not-available

Error message

Extension is already assigned to another user

What it means

Thrown by canEditExtension() (validateUserEditing.ts:21) when the VoIP/FreeSWITCH integration is enabled (VoIP_TeamCollab_SIP_Integration_Enabled), a new freeSwitchExtension value is being set, and Users.findOneByFreeSwitchExtension finds ANOTHER user already owning that extension number. Extensions are a one-user-per-number resource. Note the inverse case: when the VoIP setting is off, canEditExtension returns false and the caller instead gets the separate 'Edit user voice call extension is not allowed' error.

Source

Thrown at apps/meteor/server/lib/users/saveUser/validateUserEditing.ts:21

import type { IUser } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';

import type { UpdateUserData } from './saveUser';
import { settings } from '../../../settings';
import { hasPermissionAsync } from '../../authorization/hasPermission';

const isEditingUserRoles = (previousRoles: IUser['roles'], newRoles?: IUser['roles']) =>
	newRoles !== undefined &&
	(newRoles.some((item) => !previousRoles.includes(item)) || previousRoles.some((item) => !newRoles.includes(item)));
const isEditingField = (previousValue?: string, newValue?: string) => typeof newValue !== 'undefined' && newValue !== previousValue;

export const canEditExtension = async (newExtension?: string) => {
	if (!settings.get('VoIP_TeamCollab_SIP_Integration_Enabled')) {
		return false;
	}

	if (newExtension && (await Users.findOneByFreeSwitchExtension(newExtension, { projection: { _id: 1 } }))) {
		throw new MeteorError('error-extension-not-available', 'Extension is already assigned to another user');
	}

	return true;
};

/**
 * Validate permissions to edit user fields
 *
 * @param {string} userId
 * @param {{ _id: string, roles?: string[], username?: string, name?: string, statusText?: string, email?: string, password?: string}} userData
 */
export async function validateUserEditing(userId: IUser['_id'], userData: UpdateUserData): Promise<void> {
	const editingMyself = userData._id && userId === userData._id;

	const canEditOtherUserInfo = await hasPermissionAsync(userId, 'edit-other-user-info');
	const canEditOtherUserPassword = await hasPermissionAsync(userId, 'edit-other-user-password');
	const user = await Users.findOneById(userData._id);

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Find the current owner and release the number first: update that user's freeSwitchExtension to another value or clear it, then retry.
  2. Assign a free extension: query users and exclude all freeSwitchExtension values already taken before choosing.
  3. If the number genuinely belongs to the new user, deactivate or renumber the old holder as part of the same change.

Example fix

// before
await POST '/api/v1/users.update', { userId: newId, data: { freeSwitchExtension: '1001' } }); // throws: in use

// after: free it from the previous owner first
await POST '/api/v1/users.update', { userId: previousOwnerId, data: { freeSwitchExtension: '' } });
await POST '/api/v1/users.update', { userId: newId, data: { freeSwitchExtension: '1001' } });
Defensive patterns

Strategy: try-catch

Validate before calling

// best-effort pre-check: extensions are not indexed in a public filter, so detect collisions via admin search
const taken = new Set((await GET '/api/v1/users.list?count=0&fields={"freeSwitchExtension":1}') /* page through */).users.map((u) => u.freeSwitchExtension));
if (taken.has(ext)) throw new Error('extension in use');

Type guard

null

Try / catch

catch (e) {
  if (e.error === 'error-extension-not-available') {
    // locate owner via admin users list, release or pick another extension, then retry once
  }
}

Prevention

When it happens

Trigger: users.update assigning freeSwitchExtension '1001' when a different user already has extension '1001' in the FreeSWITCH admin panel; reassigning an extension that a departed employee still holds; importing PBX users whose extensions overlap existing accounts.

Common situations: Admin console user form with the Voice Call extension field; VoIP setup where the PBX was renumbered but Rocket.Chat users kept old extensions; two admins editing simultaneously, the second one colliding.

Related errors


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