{"record":{"id":"844b021851f01f2f","repo":"actualbudget/actual","slug":"cannot-update-schedules-with-the-same-name","errorCode":null,"errorMessage":"Cannot update schedules with the same name","messagePattern":"Cannot update schedules with the same name","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/schedules/app.ts","lineNumber":400,"sourceCode":"  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) {\n      throw new Error('Date is required');\n    }\n\n    // We need to get the full rule to merge in the updated\n    // conditions\n    rule = await getRuleForSchedule(schedule.id);\n\n    if (rule == null) {\n      // In the edge case that a rule gets corrupted (either by a bug in","sourceCodeStart":382,"sourceCodeEnd":418,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/schedules/app.ts#L382-L418","documentation":"Like creation, updates enforce schedule name uniqueness. `updateSchedule` normalizes the new name and, if a different schedule already uses it (excluding the schedule being updated via the id argument), throws.","triggerScenarios":"Calling `updateSchedule({ schedule: { id, name: 'Rent' } })` when another schedule (different id) already has the normalized name 'Rent'.","commonSituations":"Renaming a schedule to a name taken by another schedule; bulk rename scripts; sync merges assigning duplicate names.","solutions":["Choose a unique name before updating","Query existing schedule names first and pick a non-conflicting one","Normalize the name the same way (trim/lowercase) when doing your own uniqueness check"],"exampleFix":"// before\nawait updateSchedule({ schedule: { id: 's1', name: 'Utilities' } }); // s2 already 'Utilities'\n// after\nawait updateSchedule({ schedule: { id: 's1', name: 'Utilities (Studio)' } });","handlingStrategy":"validation","validationCode":"const newName = name.trim().toLowerCase();\nconst taken = (await aqlQuery(q('schedules').filter({ name: newName }))).data;\nif (taken.some(s => s.id !== id)) {\n  throw new Error('Name already used by another schedule');\n}\nawait updateSchedule({ schedule: { id, name } });","typeGuard":null,"tryCatchPattern":"try {\n  await updateSchedule({ schedule: { id, name } });\n} catch (e) {\n  if (e.message === 'Cannot update schedules with the same name') {\n    logger.warn('Name conflict on update; choosing unique name');\n    await updateSchedule({ schedule: { id, name: name + ' (copy)' } });\n  } else {\n    throw e;\n  }\n}","preventionTips":["Check name uniqueness (excluding the target schedule's own id) before renaming","Normalize names (trim/case) the same way the server does in client-side checks","Guard bulk rename operations against collisions","Show friendly uniqueness errors in custom UIs before hitting the API"],"tags":["schedule","duplicate","uniqueness-constraint"],"backgroundTag":"duplicate-schedule-name","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}