RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-shortcut

error-invalid-shortcut

Error message

Shortcut provided already exists

What it means

Thrown by saveCannedResponse when a canned response with the same shortcut already exists and would collide. Collision is detected via CannedResponse.findOneByShortcut: it fires on a create with no _id where the shortcut exists, or on an update (_id set) where a different document owns the shortcut. Code is 'error-invalid-shortcut'.

Source

Thrown at apps/meteor/ee/server/meteor-methods/saveCannedResponse.ts:61

	const canSaveDepartment = await hasPermissionAsync(userId, 'save-department-canned-responses');
	if (!canSaveAll && !canSaveDepartment && ['department'].includes(responseData.scope)) {
		throw new Meteor.Error('error-not-allowed', 'Not allowed to modify canned responses on *department* scope', {
			method: 'saveCannedResponse',
		});
	}

	// to avoid inconsistencies
	if (responseData.scope === 'user') {
		delete responseData.departmentId;
	}
	// TODO: check if the department i'm trying to save is a department i can interact with

	// check if the response already exists and we're not updating one
	const duplicateShortcut = await CannedResponse.findOneByShortcut(responseData.shortcut, {
		projection: { _id: 1 },
	});
	if ((!_id && duplicateShortcut) || (_id && duplicateShortcut && duplicateShortcut._id !== _id)) {
		throw new Meteor.Error('error-invalid-shortcut', 'Shortcut provided already exists', {
			method: 'saveCannedResponse',
		});
	}

	if (responseData.scope === 'department' && !responseData.departmentId) {
		throw new Meteor.Error('error-invalid-department', 'Invalid department', {
			method: 'saveCannedResponse',
		});
	}

	if (
		responseData.departmentId &&
		!(await LivechatDepartment.findOneById<Pick<ILivechatDepartment, '_id'>>(responseData.departmentId, { projection: { _id: 1 } }))
	) {
		throw new Meteor.Error('error-invalid-department', 'Invalid department', {
			method: 'saveCannedResponse',
		});
	}

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Check shortcut uniqueness via CannedResponse.findOneByShortcut before submit and warn the user.
  2. Enforce unique shortcut input in the UI (debounced availability check).
  3. On collision, prompt the user to pick a different shortcut or update the existing response instead.

Example fix

// before
await saveCannedResponse(userId, responseData, _id);

// after
const dup = await CannedResponse.findOneByShortcut(responseData.shortcut, { projection: { _id: 1 } });
if (dup && dup._id !== _id) {
  throw new Error('Shortcut already in use');
}
await saveCannedResponse(userId, responseData, _id);
Defensive patterns

Strategy: validation

Validate before calling

const dup = await CannedResponse.findOneByShortcut(responseData.shortcut, { projection: { _id: 1 } });
if (dup && dup._id !== _id) {
  throw new Error('Shortcut already in use');
}
await saveCannedResponse(userId, responseData, _id);

Type guard

function isShortcutValid(shortcut: string): boolean {
  return /^[a-z0-9-_]{1,32}$/i.test(shortcut);
}

Try / catch

try {
  await saveCannedResponse(userId, responseData, _id);
} catch (e) {
  if (isMeteorError(e, 'error-invalid-shortcut')) {
    notifyUser(`The shortcut "${responseData.shortcut}" is already taken.`);
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Creating a new canned response with a shortcut already in use; renaming an existing response to a shortcut owned by another response.

Common situations: User picks a shortcut like '/hi' that is already taken; import/seed data introduces duplicate shortcuts; UI does not check uniqueness inline before submit.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/c9676ddb5a1e076e. Report an issue: GitHub.