actualbudget/actual · error

Schedule not attached to a rule

Error message

Schedule not attached to a rule

What it means

`getRuleForSchedule` requires the schedule to have an attached rule id. It throws synchronously when called with `null`/`undefined` id, and the underlying lookup is only valid for schedules created through `createSchedule`, which always creates a backing rule.

Source

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

    if (
      action.op === 'set' &&
      action.field === 'amount' &&
      !action.options?.template &&
      !action.options?.formula &&
      action.value !== amount
    ) {
      changed = true;
      return { ...action, value: amount };
    }
    return action;
  });

  return changed ? updated : null;
}

export async function getRuleForSchedule(id: string | null): Promise<Rule> {
  if (id == null) {
    throw new Error('Schedule not attached to a rule');
  }

  const { data: ruleId } = await aqlQuery(
    q('schedules').filter({ id }).calculate('rule'),
  );
  return getRules().find(rule => rule.id === ruleId);
}

async function fixRuleForSchedule(id) {
  const { data: ruleId } = await aqlQuery(
    q('schedules').filter({ id }).calculate('rule'),
  );

  if (ruleId) {
    // Take the bad rule out of the system so it never causes problems
    // in the future
    await db.delete_('rules', ruleId);
  }

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Pass a valid non-null schedule id
  2. Verify the schedule row has a non-null `rule` column in the schedules table
  3. Recreate the schedule (createSchedule) if its backing rule is missing
  4. Check `getRules()` includes the rule (rules loaded) before lookup

Example fix

// before
const rule = await getRuleForSchedule(schedule.rule); // schedule.rule is null
// after
if (schedule.rule == null) {
  throw new Error('Schedule ' + schedule.id + ' has no attached rule');
}
const rule = await getRuleForSchedule(schedule.rule);
Defensive patterns

Strategy: type-guard

Validate before calling

if (schedule == null || schedule.rule == null) {
  throw new Error('Schedule has no attached rule; recreate it via createSchedule');
}
const rule = await getRuleForSchedule(schedule.rule);

Type guard

function hasAttachedRule(schedule) {
  return schedule != null && typeof schedule.id === 'string' &&
    schedule.rule != null && typeof schedule.rule === 'string';
}

Try / catch

try {
  const rule = await getRuleForSchedule(id);
} catch (e) {
  if (e.message === 'Schedule not attached to a rule') {
    logger.warn('Schedule', id, 'has no rule — skipping');
    return null;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling `getRuleForSchedule(null)` or `getRuleForSchedule(undefined)`; also reachable via `updateSchedule`/`setNextDate` when the schedule's `rule` column is null in the database.

Common situations: Manually inserted/corrupted schedule rows lacking a rule reference; calling schedule APIs on a schedule id that was partially deleted.

Related errors


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