{"record":{"id":"ffb87a0064cd74b1","repo":"actualbudget/actual","slug":"you-cannot-change-the-rule-of-a-schedule","errorCode":null,"errorMessage":"You cannot change the rule of a schedule","messagePattern":"You cannot change the rule of a schedule","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/schedules/app.ts","lineNumber":391,"sourceCode":"    rule: ruleId,\n  });\n\n  return scheduleId;\n}\n\n// TODO: don't allow deleting rules that link schedules\n\nexport async function updateSchedule({\n  schedule,\n  conditions,\n  resetNextDate,\n}: {\n  schedule: Partial<ScheduleEntity> & Pick<ScheduleEntity, 'id'>;\n  conditions?: RuleConditionEntity[];\n  resetNextDate?: boolean;\n}) {\n  if (schedule.rule) {\n    throw new Error('You cannot change the rule of a schedule');\n  }\n  const scheduleFields = { ...schedule };\n  if ('name' in scheduleFields) {\n    scheduleFields.name = normalizeScheduleName(scheduleFields.name);\n    if (\n      scheduleFields.name &&\n      (await checkIfScheduleExists(scheduleFields.name, scheduleFields.id))\n    ) {\n      throw new Error('Cannot update schedules with the same name');\n    }\n  }\n  let rule;\n\n  // This must be outside the `batchMessages` call because we change\n  // and then read data\n  if (conditions) {\n    const { date: dateCond } = extractScheduleConds(conditions);\n    if (dateCond && dateCond.value == null) {","sourceCodeStart":373,"sourceCodeEnd":409,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/schedules/app.ts#L373-L409","documentation":"`updateSchedule` refuses updates where `schedule.rule` is set, because the rule attached to a schedule is created once at schedule creation and is not meant to be swapped. Changing conditions should be done via the `conditions` parameter instead.","triggerScenarios":"Calling `updateSchedule({ id, rule: 'some-rule-id', ... })` with a `rule` property present on the schedule object — commonly from spreading a full ScheduleEntity (which includes `rule`) into the update payload.","commonSituations":"API clients that fetch a schedule and pass the whole object back into updateSchedule without deleting the read-only `rule` field.","solutions":["Remove the `rule` property from the object passed to updateSchedule","Update rule behavior via the `conditions` parameter or by editing the rule itself","Destructure only mutable fields (`id`, `name`, `completed`, etc.) before updating"],"exampleFix":"// before\nawait updateSchedule({ schedule: fetchedSchedule }); // includes .rule -> throws\n// after\nconst { rule, ...editable } = fetchedSchedule;\nawait updateSchedule({ schedule: editable });","handlingStrategy":"validation","validationCode":"if ('rule' in schedule) {\n  const { rule, ...editable } = schedule;\n  schedule = editable;\n}\nawait updateSchedule({ schedule });","typeGuard":"function isUpdatableSchedule(s) {\n  return s != null && typeof s.id === 'string' && !('rule' in s);\n}","tryCatchPattern":"try {\n  await updateSchedule({ schedule });\n} catch (e) {\n  if (e.message === 'You cannot change the rule of a schedule') {\n    logger.warn('Stripped read-only rule field and retried');\n    const { rule, ...rest } = schedule;\n    await updateSchedule({ schedule: rest });\n  } else {\n    throw e;\n  }\n}","preventionTips":["Never include the `rule` property in updateSchedule payloads","Destructure/pick only mutable fields before sending updates","Treat rule changes as create-new-schedule + delete-old, not an update","Type update payloads as Omit<ScheduleEntity, 'rule'> where possible"],"tags":["schedule","immutable-field","api-misuse"],"backgroundTag":"immutable-field-update","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}