RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-department

error-invalid-department

Error message

Invalid department

What it means

Thrown by the saveCannedResponse method when a canned response is saved with scope === 'department' but no departmentId value is supplied. Rocket.Chat requires a department-scoped canned response to be tied to a concrete LivechatDepartment record, so a missing departmentId is treated as invalid input rather than defaulted. This fires before any DB lookup of the department.

Source

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

	// 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',
		});
	}

	let result: Omit<IOmnichannelCannedResponse, '_updatedAt' | '_createdAt'> & { _createdAt?: Date };

	if (_id) {
		const cannedResponse = await CannedResponse.findOneById(_id);
		if (!cannedResponse) {

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. When scope === 'department', always send a non-empty departmentId alongside the request.
  2. If the caller does not have a department, set scope to 'user' or 'global' instead of 'department'.
  3. In the form layer, make the department field required (and re-enabled) whenever the scope selector equals 'department'.
  4. Unit-test the save path with scope:'department' and an empty departmentId to fail fast at the API boundary.

Example fix

// before
await saveCannedResponse(userId, { shortcut: 'hi', text: 'Hello', scope: 'department' });

// after
await saveCannedResponse(userId, { shortcut: 'hi', text: 'Hello', scope: 'department', departmentId });
Defensive patterns

Strategy: validation

Validate before calling

function validateCannedResponseInput(data: { scope: string; departmentId?: string }): string | null {
  if (data.scope === 'department' && !data.departmentId) {
    return 'A departmentId is required when scope is "department".';
  }
  return null;
}
// const err = validateCannedResponseInput(data); if (err) throw new Error(err);

Type guard

function isDepartmentScopedPayload(data: unknown): data is { scope: 'department'; departmentId: string } {
  return typeof data === 'object' && data !== null
    && (data as any).scope === 'department'
    && typeof (data as any).departmentId === 'string'
    && (data as any).departmentId.length > 0;
}

Prevention

When it happens

Trigger: Calling saveCannedResponse (or the cannedResponse.save meteor method / REST endpoint that delegates to it) with responseData.scope set to the literal string 'department' while omitting or leaving empty responseData.departmentId.

Common situations: UI form that lets the agent pick a scope dropdown but does not require the department selector when 'department' is chosen; a migration or seed script that hard-codes scope:'department' without setting departmentId; front-end bug where the department select is disabled/hidden but its value is not cleared.

Related errors


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