RocketChat/Rocket.Chat · error · Meteor.Error
error-invalid-shortcut
error-invalid-shortcut
Error message
Shortcut provided already exists
What it means
saveCannedResponse (saveCannedResponse.ts:61) enforces shortcut uniqueness across the whole CannedResponse collection: CannedResponse.findOneByShortcut must not find an existing document (or, on update, must find the same _id). Creating a response with a shortcut that already exists, or updating one to a shortcut owned by a different response, throws error-invalid-shortcut.
Solutions
- Pick a different, unique shortcut (e.g. prefix it: !dept-hello)
- On update, either keep the current shortcut or verify the target shortcut is free
- Pre-check with CannedResponse.findOneByShortcut before submitting the form and surface the conflict early
Example fix
// before
const responseData = { shortcut: '!hello', text: 'Hi!', scope: 'user' };
await saveCannedResponse(uid, responseData);
// after
const dup = await CannedResponse.findOneByShortcut(responseData.shortcut, { projection: { _id: 1 } });
if (dup && dup._id !== _id) {
throw new Meteor.Error('error-invalid-shortcut', 'Shortcut provided already exists');
}
await saveCannedResponse(uid, responseData); Defensive patterns
Strategy: validation
Validate before calling
const duplicate = await CannedResponse.findOneByShortcut(responseData.shortcut, { projection: { _id: 1 } });
if ((!_id && duplicate) || (_id && duplicate && duplicate._id !== _id)) {
throw new Meteor.Error('error-invalid-shortcut', 'Shortcut provided already exists');
}
await saveCannedResponse(userId, responseData, _id); Try / catch
try {
await saveCannedResponse(userId, responseData, _id);
} catch (e) {
if (e instanceof Meteor.Error && e.error === 'error-invalid-shortcut') {
// prompt for a different shortcut; uniqueness is collection-wide
}
throw e;
} Prevention
- Check shortcut uniqueness while the user types and before submit
- Namespace shortcuts per team/department (e.g. !sales-hello) to avoid collisions
- Remember uniqueness spans all scopes, including other users' responses
When it happens
Trigger: Creating a canned response whose shortcut collides with an existing one in any scope (user/department/global — the lookup is not scope-filtered); or updating response A to the shortcut currently used by response B (duplicateShortcut._id !== _id).
Common situations: Two agents independently pick !hello; import/migration scripts inserting duplicate shortcuts; renaming an existing response's shortcut to a taken value; department-specific intent foiled by the global uniqueness rule.
Related errors
- Custom_User_Status_Error_Name_Already_In_Use
- error-business-hour-name-already-in-use
- error-canned-response-not-found
- error-canned-response-not-found
- error-duplicated-sla
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/c9676ddb5a1e076e.
Report an issue: GitHub.
Appendix: 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 b2c16d5842)