{"record":{"id":"01d8e0a70c967b0e","repo":"actualbudget/actual","slug":"unhandled-template-type","errorCode":null,"errorMessage":"Unhandled template type","messagePattern":"Unhandled template type","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/components/modals/BudgetAutomationsModal/migrateTemplatesToAutomations.ts","lineNumber":45,"sourceCode":"    case 'refill':\n      return 'refill';\n    case 'average':\n    case 'copy':\n      return 'historical';\n    case 'by':\n    case 'spend':\n      return 'by';\n    case 'remainder':\n      return 'remainder';\n    case 'goal':\n      return 'goal';\n    case 'error':\n      // filtered upstream by hasUnsupportedDirective; surface if it ever isn't\n      throw new Error(`Unsupported template type reached migration`);\n    default: {\n      const _exhaustive: never = template;\n      void _exhaustive;\n      throw new Error(`Unhandled template type`);\n    }\n  }\n}\n\n// Fill in scheduleId from name (or refresh a stale name from scheduleId) so\n// the editor and engine consistently see both.\nfunction hydrateScheduleTemplate(\n  template: ScheduleTemplate,\n  schedules: readonly ScheduleEntity[],\n): ScheduleTemplate {\n  const schedule = template.scheduleId\n    ? schedules.find(s => s.id === template.scheduleId)\n    : template.name\n      ? schedules.find(s => s.name?.trim() === template.name?.trim())\n      : undefined;\n  if (!schedule) return template;\n  return {\n    ...template,","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/components/modals/BudgetAutomationsModal/migrateTemplatesToAutomations.ts#L27-L63","documentation":"The default branch of getDisplayTypeFromTemplate uses a `const _exhaustive: never = template` assertion: for a well-typed Template union this branch is unreachable, so reaching it means template.type held a value outside the known Template type union at runtime. It is a compile-time-exhaustiveness safety net that catches untyped or corrupt template data during the templates-to-automations migration.","triggerScenarios":"Migrating a template list containing a Template whose `type` is not any known literal (e.g. data from an older budget format, a hand-edited note directive parsed into an unexpected type, or an object cast to Template incorrectly).","commonSituations":"Version skew between stored budget data and the current Template type union; plugins/custom code building Template objects; a parser regression emitting a type value the union no longer contains.","solutions":["Log JSON.stringify(template) at the throw site to capture the rogue type value.","Add a case for the new/unknown template type if it is legitimate, mapping it to the right DisplayTemplateType.","Validate/normalize templates (e.g. filter to known types) before calling migrateTemplatesToAutomations.","Fix the upstream parser so it only produces Template types present in the type union."],"exampleFix":"// before\ntemplates.forEach(t => migrate(t));\n// after\ntemplates.filter(t => KNOWN_TEMPLATE_TYPES.has(t.type)).forEach(t => migrate(t));","handlingStrategy":"validation","validationCode":"const KNOWN_TYPES = new Set(['percentage','schedule','periodic','simple','limit','refill','average','copy','by','spend','remainder','goal']);\nconst safe = templates.filter(t => KNOWN_TYPES.has((t as Template).type));\nmigrateTemplatesToAutomations(safe, schedules);","typeGuard":"function hasKnownType(t: Template): boolean {\n  return KNOWN_TEMPLATE_TYPE_UNION.includes(t.type as never);\n}","tryCatchPattern":"try {\n  migrateTemplatesToAutomations(templates, schedules);\n} catch (err) {\n  if (String(err).includes('Unhandled template type')) {\n    logger.error('Unrecognized template during migration', err);\n    return partialMigrationResult;\n  }\n  throw err;\n}","preventionTips":["Normalize/deserialize templates through a validated parser that only emits known union members.","When adding a new Template type, update this switch, the type union, and the parser in one change.","Keep the `const _exhaustive: never` assertion in every consumer switch of Template.","Test migration against fixtures from all historical budget data formats."],"tags":["typescript","exhaustiveness","migration","budget-templates"],"backgroundTag":"unhandled-switch-case","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}