{"record":{"id":"7c9b391299fba1b4","repo":"actualbudget/actual","slug":"schedule-not-found-id","errorCode":null,"errorMessage":"Schedule not found: ${id}","messagePattern":"Schedule not found: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/db/index.ts","lineNumber":997,"sourceCode":"    }\n  });\n}\n\n/**\n * Move a schedule to a new position among all non-tombstoned schedules.\n * Uses the same midpoint/shove algorithm as transaction reordering.\n *\n * @param id - The ID of the schedule to move\n * @param targetId - The ID of the schedule to place AFTER, or null to place at top\n */\nexport async function moveSchedule(id: string, targetId: string | null) {\n  await batchMessages(async () => {\n    const schedule = await first<{ id: string }>(\n      'SELECT id FROM schedules WHERE id = ? AND tombstone = 0',\n      [id],\n    );\n    if (!schedule) {\n      throw new Error(`Schedule not found: ${id}`);\n    }\n\n    const schedules = await all<{ id: string; sort_order: number }>(\n      `SELECT id, sort_order FROM schedules\n       WHERE tombstone = 0\n       ORDER BY sort_order DESC, id`,\n    );\n\n    const { sort_order: newSortOrder, updates } = shoveSortOrdersDescending(\n      schedules,\n      targetId,\n      id,\n    );\n\n    for (const info of updates) {\n      await update('schedules', info);\n    }\n","sourceCodeStart":979,"sourceCodeEnd":1015,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/db/index.ts#L979-L1015","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait aqlQuery.moveSchedule(scheduleId, targetId);\n// after\nconst sched = await aqlQuery.getSchedule(scheduleId);\nif (sched) await aqlQuery.moveSchedule(scheduleId, targetId);","handlingStrategy":"try-catch","validationCode":"const sched = await aqlQuery.getSchedule(scheduleId);\nif (!sched) throw new Error(`Schedule ${scheduleId} no longer exists`);","typeGuard":"function scheduleExists(s: unknown): s is { id: string } {\n  return !!s && typeof (s as any).id === 'string';\n}","tryCatchPattern":"try {\n  await aqlQuery.moveSchedule(scheduleId, targetId);\n} catch (e) {\n  if (e.message.startsWith('Schedule not found')) {\n    removePendingOp('moveSchedule', scheduleId);\n  } else throw e;\n}","preventionTips":["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"],"tags":["not-found","schedules","stale-data","sync"],"backgroundTag":"entity-not-found","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}