{"record":{"id":"f8263c985513baff","repo":"actualbudget/actual","slug":"failed-updating-the-rule","errorCode":null,"errorMessage":"Failed updating the rule","messagePattern":"Failed updating the rule","errorType":"exception","errorClass":"APIError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/api.ts","lineNumber":911,"sourceCode":"handlers['api/rule-create'] = withMutation(async function ({ rule }) {\n  checkFileOpen();\n  const addedRule = await handlers['rule-add'](ruleModel.fromExternal(rule));\n\n  if ('error' in addedRule) {\n    throw APIError('Failed creating a new rule', addedRule.error);\n  }\n\n  return addedRule;\n});\n\nhandlers['api/rule-update'] = withMutation(async function ({ rule }) {\n  checkFileOpen();\n  const updatedRule = await handlers['rule-update'](\n    ruleModel.fromExternal(rule),\n  );\n\n  if ('error' in updatedRule) {\n    throw APIError('Failed updating the rule', updatedRule.error);\n  }\n\n  return updatedRule;\n});\n\nhandlers['api/rule-delete'] = withMutation(async function (id) {\n  checkFileOpen();\n  return handlers['rule-delete'](id);\n});\n\nhandlers['api/schedules-get'] = async function () {\n  checkFileOpen();\n  const { data } = await aqlQuery(q('schedules').select('*'));\n  const schedules = data as ScheduleEntity[];\n  return schedules.map(schedule => scheduleModel.toExternal(schedule));\n};\n\nhandlers['api/schedule-create'] = withMutation(async function (","sourceCodeStart":893,"sourceCodeEnd":929,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/api.ts#L893-L929","documentation":"api.updateRule() pushes the converted rule through the internal rule-update handler. When that handler returns { error } (rule not found or invalid content), the API throws this APIError carrying the underlying error.","triggerScenarios":"Calling api.updateRule(rule) with a rule id that doesn't exist in the budget, or with conditions/actions failing validation (unknown fields, bad operators, empty actions).","commonSituations":"Updating a rule from a stale id after the budget was re-imported/rolled back; mutating a rule fetched in a previous session before a file switch; same validation pitfalls as rule creation.","solutions":["Confirm the rule id exists via api.getRules() before updating","Check the APIError's cause for the underlying validation failure and correct the conditions/actions","Re-fetch the rule with a fresh get and modify its fields rather than reusing cached payloads"],"exampleFix":"// before\nawait api.updateRule(cachedRule);\n// after\nconst rules = await api.getRules();\nconst fresh = rules.find(r => r.id === cachedRule.id);\nif (fresh) await api.updateRule({ ...fresh, ...changes });","handlingStrategy":"try-catch","validationCode":"const parsed = ruleModel.fromExternal(rule);\nconst res = await handlers['rule-update'](parsed);\nif ('error' in res) preflightCheck(res.error);","typeGuard":"function isRuleError(r) {\n  return r != null && typeof r === 'object' && 'error' in r;\n}","tryCatchPattern":"const updated = await ruleUpdate(rule);\nif (isRuleError(updated)) {\n  throw new APIError('Failed updating the rule', updated.error);\n}","preventionTips":["Confirm rule existence before update","Reuse the same validator as creation for conditions/actions","Log the wrapped cause with the rule id"],"tags":["api","rules","validation"],"backgroundTag":"entity-validation-failed","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}