actualbudget/actual · error
Schedule not found: ${id}
Error message
Schedule not found: ${id} What it means
The schedule move/reorder logic verifies the schedule exists and is not tombstoned via SELECT id FROM schedules WHERE id = ? AND tombstone = 0; if no row is returned it throws 'Schedule not found: <id>'. Like the transaction move, it runs inside batchMessages so nothing is written when the check fails.
Source
Thrown at packages/loot-core/src/server/db/index.ts:997
}
});
}
/**
* Move a schedule to a new position among all non-tombstoned schedules.
* Uses the same midpoint/shove algorithm as transaction reordering.
*
* @param id - The ID of the schedule to move
* @param targetId - The ID of the schedule to place AFTER, or null to place at top
*/
export async function moveSchedule(id: string, targetId: string | null) {
await batchMessages(async () => {
const schedule = await first<{ id: string }>(
'SELECT id FROM schedules WHERE id = ? AND tombstone = 0',
[id],
);
if (!schedule) {
throw new Error(`Schedule not found: ${id}`);
}
const schedules = await all<{ id: string; sort_order: number }>(
`SELECT id, sort_order FROM schedules
WHERE tombstone = 0
ORDER BY sort_order DESC, id`,
);
const { sort_order: newSortOrder, updates } = shoveSortOrdersDescending(
schedules,
targetId,
id,
);
for (const info of updates) {
await update('schedules', info);
}
View on GitHub (pinned to d4334cb6e6)
Solutions
- Confirm the schedule id with getSchedule / query schedules before moving
- Re-sync so the client drops references to tombstoned schedules
- Remove the queued reorder operation if the schedule was intentionally deleted
- Ensure the id passed is a schedule id, not a rule or transaction id
Example fix
// before await aqlQuery.moveSchedule(scheduleId, targetId); // after const sched = await aqlQuery.getSchedule(scheduleId); if (sched) await aqlQuery.moveSchedule(scheduleId, targetId);
Defensive patterns
Strategy: try-catch
Validate before calling
const sched = await aqlQuery.getSchedule(scheduleId);
if (!sched) throw new Error(`Schedule ${scheduleId} no longer exists`); Type guard
function scheduleExists(s: unknown): s is { id: string } {
return !!s && typeof (s as any).id === 'string';
} Try / catch
try {
await aqlQuery.moveSchedule(scheduleId, targetId);
} catch (e) {
if (e.message.startsWith('Schedule not found')) {
removePendingOp('moveSchedule', scheduleId);
} else throw e;
} Prevention
- Validate schedule ids against current state before reorder operations
- Prune queued UI operations for schedules deleted via sync
- Confirm the id is a schedule id, not a rule/payee id
- Re-sync after deletions to refresh the schedule list
When it happens
Trigger: Calling a schedule move/reorder API with an unknown id, a schedule deleted locally or on another device, or a stale id from client cache after a sync deleted the schedule.
Common situations: Two devices racing: one deletes a rule/schedule while the user on the other device reorders it; offline queue replaying reorder ops for removed schedules; typo'd or wrong entity id passed in.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- Transaction not found: ${id}
- Schedule ${id} not found
- Sync ID is required for sync ${flag}. Set --sync-id or ACTUA
- Could not resolve on-disk budget id for syncId ${syncId} aft
- TrieNode for key ${k} could not be found
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/7c9b391299fba1b4.
Report an issue: GitHub.