{"record":{"id":"99d9090d387a4e92","repo":"actualbudget/actual","slug":"unresolved-cleanup-group-for-overspend-row-row","errorCode":null,"errorMessage":"Unresolved cleanup group for overspend row: ${row.group}","messagePattern":"Unresolved cleanup group for overspend row: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/budget/cleanup-template-notes.ts","lineNumber":103,"sourceCode":"}\n\nfunction toCleanupTemplate(\n  row: ParsedCleanupRow,\n  nameToId: Map<string, string>,\n): CleanupTemplate {\n  switch (row.type) {\n    case 'source':\n      return { role: 'source', groupId: resolveGroup(row.group, nameToId) };\n    case 'sink':\n      return {\n        role: 'sink',\n        groupId: resolveGroup(row.group, nameToId),\n        weight: row.weight,\n      };\n    case 'overspend': {\n      const groupId = nameToId.get(row.group.toLowerCase());\n      if (groupId == null) {\n        throw new Error(\n          `Unresolved cleanup group for overspend row: ${row.group}`,\n        );\n      }\n      return { role: 'overspend', groupId };\n    }\n    default:\n      throw new Error(`Unknown cleanup row type: ${String(row)}`);\n  }\n}\n\nfunction resolveGroup(\n  name: string | null,\n  nameToId: Map<string, string>,\n): string | null {\n  return name != null ? (nameToId.get(name.toLowerCase()) ?? null) : null;\n}\n\nasync function resolveCleanupGroups(","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/budget/cleanup-template-notes.ts#L85-L121","documentation":"When converting a cleanup template 'overspend' row into its internal form, the row's group name must resolve to an existing cleanup group via the case-insensitive name→id map. If the group referenced by the overspend row was never defined (or was defined with a different name/deleted), this error is thrown. It indicates a dangling reference between cleanup rows and cleanup group definitions.","triggerScenarios":"A cleanup template contains an `overspend` row referencing `row.group` but no matching `group` definition row exists in nameToId — e.g. the group definition line was removed, renamed, or the overspend row has a typo in the group name.","commonSituations":"Editing budget template notes and renaming a group without updating the overspend rows that reference it, deleting a cleanup group while overspend rules still point at it, or case/whitespace mismatches (lookup lowercases row.group).","solutions":["Add or restore the cleanup group definition whose name matches the overspend row's group.","Fix the overspend row's group name to exactly match an existing group (case-insensitive, trimmed).","Remove the orphaned overspend row if the group is intentionally gone.","List existing cleanup groups (cleanup_groups table / API) and align names before applying the template."],"exampleFix":"// before\n#cleanup overspend Groceries-caps   // group 'Groceries-caps' undefined\n// after (group defined first)\n#cleanup group Groceries\n#cleanup overspend Groceries","handlingStrategy":"validation","validationCode":"const groups = new Set(rows.filter(r => r.type === 'group').map(r => r.name.toLowerCase()));\nfor (const row of rows.filter(r => r.type === 'overspend')) {\n  if (!groups.has(row.group.toLowerCase())) {\n    throw new Error(`overspend row references undefined group: ${row.group}`);\n  }\n}\nconst template = toCleanupTemplate(rows);","typeGuard":"function isKnownGroup(row: { group: string }, nameToId: Map<string, string>): boolean {\n  return nameToId.has(row.group.toLowerCase());\n}","tryCatchPattern":"try {\n  const cleanup = toCleanupTemplate(rows);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Unresolved cleanup group for overspend row')) {\n    logger.warn('Dropping overspend row with unresolved group', { error: e.message });\n  } else {\n    throw e;\n  }\n}","preventionTips":["Define every cleanup group before any overspend row that references it","Update all references together when renaming a group","Delete orphaned overspend rows when removing a group","Cross-check group names in template notes with a validation script before applying"],"tags":["budget-cleanup","dangling-reference","validation"],"backgroundTag":"unresolved-reference","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}