actualbudget/actual · error

No rule found for schedule

Error message

No rule found for schedule

What it means

`setNextDate` looks up the schedule's rule only when `conditions` were not supplied; if the resulting rule is null (rule id points to a deleted/missing rule) it throws 'No rule found for schedule'. This guards against computing the next date from nonexistent conditions.

Source

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

}

export async function setNextDate({
  id,
  start,
  conditions,
  reset,
  skipRequested,
}: {
  id: string;
  start?;
  conditions?;
  reset?: boolean;
  skipRequested?: boolean;
}) {
  if (conditions == null) {
    const rule = await getRuleForSchedule(id);
    if (rule == null) {
      throw new Error('No rule found for schedule');
    }
    conditions = rule.serialize().conditions;
  }

  const { date: dateCond } = extractScheduleConds(conditions);

  let { data: nextDate } = await aqlQuery(
    q('schedules').filter({ id }).calculate('next_date'),
  );

  if (skipRequested === true) {
    const skipWeekend: boolean = dateCond.value?.skipWeekend;
    const weekendSolveMode: string = dateCond.value?.weekendSolveMode;

    if (weekendSolveMode === 'before' && skipWeekend === true) {
      const parsedNextDate = parseDate(nextDate);
      if (d.isFriday(parsedNextDate) || d.isWeekend(parsedNextDate)) {
        // nextDate is on weekend or friday, moving to monday

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Pass explicit `conditions` containing a valid date condition to `setNextDate` so the rule lookup is skipped
  2. Check the schedules table `rule` column references an existing rule id
  3. Recreate the rule for the schedule or delete and recreate the schedule
  4. Pass `reset: true` with conditions to reinitialize the schedule

Example fix

// before
await setNextDate(scheduleId, { reset: true }); // rule missing -> throws
// after
await setNextDate(scheduleId, {
  reset: true,
  conditions: [
    { field: 'date', op: 'isapprox', value: { frequency: 'monthly', start: '2026-01-01' } },
  ],
});
Defensive patterns

Strategy: validation

Validate before calling

const ruleId = (await aqlQuery(q('schedules').filter({ id }).calculate('rule'))).data;
if (!conditions && ruleId == null) {
  throw new Error('Cannot setNextDate: schedule has no rule; supply conditions explicitly');
}
await setNextDate(id, { conditions: dateConditions });

Type guard

function isRuleWithConditions(rule) {
  return rule != null &&
    Array.isArray(rule.serialize().conditions) &&
    rule.serialize().conditions.some(c => c.field === 'date');
}

Try / catch

try {
  await setNextDate(id, { reset: true });
} catch (e) {
  if (e.message === 'No rule found for schedule') {
    logger.warn('Orphaned schedule', id, '— recreating rule');
    await recreateScheduleRule(id);
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling `setNextDate(id, ...)` without `conditions` when the schedule's backing rule no longer exists (deleted rule, stale rule id), via updateSchedule, skipNextDate, or the schedules advancement service.

Common situations: Rules deleted out-of-band while schedules remained; sync conflicts removing the rule but not the schedule; database restored partially.

Related errors


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