RocketChat/Rocket.Chat · error · Error

Business hour ${department.businessHourId} not found

Error message

Business hour ${department.businessHourId} not found

What it means

In onDepartmentDisabled, when the disabled department was the last one using the business hour, the hour is disabled and then re-fetched with findOneById(department.businessHourId) before reopening logic. If that re-fetch returns null, it throws 'Business hour ${id} not found'.

Source

Thrown at apps/meteor/ee/server/lib/omnichannel/business-hour/Multiple.ts:206

		const defaultBH = await this.BusinessHourRepository.findOneDefaultBusinessHour();
		if (!defaultBH) {
			throw new Error('Default business hour not found');
		}
		await this.UsersRepository.closeAgentsBusinessHoursByBusinessHourIds([businessHour._id, defaultBH._id]);

		// If i'm the only one, disable the business hour
		const imTheOnlyOne = !(await LivechatDepartment.countByBusinessHourIdExcludingDepartmentId(businessHour._id, department._id));
		if (imTheOnlyOne) {
			bhLogger.warn({
				msg: 'onDepartmentDisabled: department is the only one on business hour, disabling it',
				departmentId: department._id,
				businessHourId: businessHour._id,
			});
			await this.BusinessHourRepository.disableBusinessHour(businessHour._id);

			businessHour = await this.BusinessHourRepository.findOneById(department.businessHourId);
			if (!businessHour) {
				throw new Error(`Business hour ${department.businessHourId} not found`);
			}
		}

		// start default business hour and this BH if needed
		if (!settings.get('Livechat_enable_business_hours')) {
			return;
		}

		const businessHourToOpen = await filterBusinessHoursThatMustBeOpened([businessHour, defaultBH]);
		for await (const bh of businessHourToOpen) {
			await openBusinessHour(bh, false);
		}

		await makeAgentsUnavailableBasedOnBusinessHour();
		await businessHourManager.restartCronJobsIfNecessary();
	}

	async onDepartmentArchived(department: Pick<ILivechatDepartment, '_id' | 'businessHourId'>): Promise<void> {

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Ensure departments are unlinked from a business hour before deleting that hour
  2. Clear stale businessHourId references on departments (set to null) so onDepartmentDisabled does not chase missing records
  3. Retry the department-disable after the concurrent business-hour operation settles
Defensive patterns

Strategy: try-catch

Validate before calling

const bh = await BusinessHourRepository.findOneById(department.businessHourId);
if (!bh) {
	// clear the stale reference: await LivechatDepartment.removeBusinessHourFromDepartmentsByIdsAndBusinessHourId([department._id], department.businessHourId);
}
await onDepartmentDisabled(department);

Try / catch

try {
	await onDepartmentDisabled(department);
} catch (err) {
	if (err instanceof Error && /^Business hour .+ not found$/.test(err.message)) {
		// stale businessHourId on the department: clear the reference and retry
	} else {
		throw err;
	}
}

Prevention

When it happens

Trigger: Disabling the last department of a business hour while the business-hour record disappears between the disable and the re-fetch (concurrent removal or data inconsistency).

Common situations: Concurrent admin actions deleting the same business hour; inconsistent data after partial migration; the department still referencing a deleted business hour id.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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