actualbudget/actual · error

Cannot update schedules with the same name

Error message

Cannot update schedules with the same name

What it means

Like creation, updates enforce schedule name uniqueness. `updateSchedule` normalizes the new name and, if a different schedule already uses it (excluding the schedule being updated via the id argument), throws.

Source

Thrown at packages/loot-core/src/server/schedules/app.ts:400

  schedule,
  conditions,
  resetNextDate,
}: {
  schedule: Partial<ScheduleEntity> & Pick<ScheduleEntity, 'id'>;
  conditions?: RuleConditionEntity[];
  resetNextDate?: boolean;
}) {
  if (schedule.rule) {
    throw new Error('You cannot change the rule of a schedule');
  }
  const scheduleFields = { ...schedule };
  if ('name' in scheduleFields) {
    scheduleFields.name = normalizeScheduleName(scheduleFields.name);
    if (
      scheduleFields.name &&
      (await checkIfScheduleExists(scheduleFields.name, scheduleFields.id))
    ) {
      throw new Error('Cannot update schedules with the same name');
    }
  }
  let rule;

  // This must be outside the `batchMessages` call because we change
  // and then read data
  if (conditions) {
    const { date: dateCond } = extractScheduleConds(conditions);
    if (dateCond && dateCond.value == null) {
      throw new Error('Date is required');
    }

    // We need to get the full rule to merge in the updated
    // conditions
    rule = await getRuleForSchedule(schedule.id);

    if (rule == null) {
      // In the edge case that a rule gets corrupted (either by a bug in

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Choose a unique name before updating
  2. Query existing schedule names first and pick a non-conflicting one
  3. Normalize the name the same way (trim/lowercase) when doing your own uniqueness check

Example fix

// before
await updateSchedule({ schedule: { id: 's1', name: 'Utilities' } }); // s2 already 'Utilities'
// after
await updateSchedule({ schedule: { id: 's1', name: 'Utilities (Studio)' } });
Defensive patterns

Strategy: validation

Validate before calling

const newName = name.trim().toLowerCase();
const taken = (await aqlQuery(q('schedules').filter({ name: newName }))).data;
if (taken.some(s => s.id !== id)) {
  throw new Error('Name already used by another schedule');
}
await updateSchedule({ schedule: { id, name } });

Try / catch

try {
  await updateSchedule({ schedule: { id, name } });
} catch (e) {
  if (e.message === 'Cannot update schedules with the same name') {
    logger.warn('Name conflict on update; choosing unique name');
    await updateSchedule({ schedule: { id, name: name + ' (copy)' } });
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling `updateSchedule({ schedule: { id, name: 'Rent' } })` when another schedule (different id) already has the normalized name 'Rent'.

Common situations: Renaming a schedule to a name taken by another schedule; bulk rename scripts; sync merges assigning duplicate names.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/844b021851f01f2f. Report an issue: GitHub.