actualbudget/actual · error

Cannot create schedules with the same name

Error message

Cannot create schedules with the same name

What it means

`createSchedule` normalizes the schedule name and checks uniqueness via `checkIfScheduleExists`. If another schedule already uses the same (normalized) name, creation is rejected to keep schedule names unique for user clarity.

Source

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

  const { date: dateCond } = extractScheduleConds(conditions);
  if (dateCond == null) {
    throw new Error('A date condition is required to create a schedule');
  }
  if (dateCond.value == null) {
    throw new Error('Date is required');
  }

  const nextDate = getNextDate(dateCond);
  const nextDateRepr = nextDate ? toDateRepr(nextDate) : null;
  const scheduleFields = schedule && {
    ...schedule,
    name: normalizeScheduleName(schedule.name),
  };
  if (scheduleFields) {
    if (scheduleFields.name) {
      if (await checkIfScheduleExists(scheduleFields.name, scheduleId)) {
        throw new Error('Cannot create schedules with the same name');
      }
    }
  }

  // Create the rule here based on the info
  const ruleId = await insertRule({
    stage: null,
    conditionsOp: 'and',
    conditions,
    actions: [{ op: 'link-schedule', value: scheduleId }],
  });

  const now = Date.now();
  await db.insertWithUUID('schedules_next_date', {
    schedule_id: scheduleId,
    local_next_date: nextDateRepr,
    local_next_date_ts: now,
    base_next_date: nextDateRepr,

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Pick a unique name or append a suffix before calling createSchedule
  2. Check for an existing schedule with the same normalized name first and update it instead
  3. Call `updateSchedule` rather than `createSchedule` when the schedule already exists
  4. List schedules (`getRules`/schedule queries) to find the conflicting name

Example fix

// before
await createSchedule({ name: 'Rent', conditions }); // may already exist
// after
const existing = (await aqlQuery(q('schedules').filter({ name: 'rent' }))).data;
if (existing.length === 0) {
  await createSchedule({ name: 'Rent', conditions });
}
Defensive patterns

Strategy: validation

Validate before calling

const name = schedule.name.trim().toLowerCase();
const dup = (await aqlQuery(q('schedules').filter({ name }))).data;
if (dup.length > 0) {
  schedule.name = `${schedule.name} (${new Date().toISOString().slice(0, 10)})`;
}
await createSchedule({ ...schedule, conditions });

Try / catch

try {
  await createSchedule({ schedule, conditions });
} catch (e) {
  if (e.message === 'Cannot create schedules with the same name') {
    await createSchedule({ ...schedule, name: schedule.name + ' (2)' }, { conditions });
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling `createSchedule` with `schedule.name` equal (case/whitespace-insensitively after normalization) to an existing schedule's name, when a schedule payload with a name is provided.

Common situations: Automations creating recurring schedules (e.g. 'Rent') that already exist; importing schedules twice; syncing devices that both created the same named schedule.

Related errors


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