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
- Pass a valid non-null schedule id
- Verify the schedule row has a non-null `rule` column in the schedules table
- Recreate the schedule (createSchedule) if its backing rule is missing
- 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
- Only create schedules through createSchedule so a backing rule always exists
- Never null out the schedules.rule column manually
- Check schedule.rule != null before any rule-dependent schedule API
- When deleting rules, delete or detach their schedules too
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
- No rule found for schedule
- Transfer payee with account ID ${id} not found.
- Transfer payee with account ID ${transferAccountId} not foun
- Subtransaction amount is invalid, must be an integer: ${sub.
- Unrecognized bank-sync provider: ${acctRow.account_sync_sour
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/0fb09c52fd7a468e.
Report an issue: GitHub.