actualbudget/actual · error

Unrecognized menu option: ${String(name)}

Error message

Unrecognized menu option: ${String(name)}

What it means

IncomeMenu's onSelect switch handles its menu options (e.g. 'carryover') and throws on any other name via `Unrecognized menu option: ${String(name)}`. Like BalanceMenu/BudgetMenu, it is an exhaustiveness guard assuming items and switch cases stay synchronized.

Source

Thrown at packages/desktop-client/src/components/budget/envelope/IncomeMenu.tsx:47

  return (
    <span>
      <Menu
        onMenuSelect={name => {
          switch (name) {
            case 'view':
              onShowActivity(categoryId, month);
              break;
            case 'carryover':
              if (!carryover) onBudgetAction(month, 'reset-hold');
              onBudgetAction(month, 'carryover', {
                category: categoryId,
                flag: !carryover,
              });
              onClose();
              break;
            default:
              throw new Error(`Unrecognized menu option: ${String(name)}`);
          }
        }}
        items={[
          {
            name: 'carryover',
            text: carryover ? t('Disable auto hold') : t('Enable auto hold'),
          },
          {
            name: 'view',
            text: t('View transactions'),
          },
        ]}
      />
    </span>
  );
}

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Add the missing case to IncomeMenu's onSelect switch for the option name in question.
  2. Share a string-literal union between item definitions and onSelect so TypeScript reports unmatched names.
  3. Replace the throw with a logged warning + early return if a no-op option is acceptable in some contexts.

Example fix

// before
default:
  throw new Error(`Unrecognized menu option: ${String(name)}`);

// after
case 'reset-income':
  onResetIncome?.();
  break;
default:
  console.warn('Unknown income menu option', name);
Defensive patterns

Strategy: type-guard

Validate before calling

type IncomeMenuOption = 'carryover' | 'report-budget';
const INCOME_MENU_OPTIONS: readonly IncomeMenuOption[] = ['carryover', 'report-budget'];
if (!INCOME_MENU_OPTIONS.includes(name as IncomeMenuOption)) return;

Type guard

function isIncomeMenuOption(name: string): name is IncomeMenuOption {
  return (INCOME_MENU_OPTIONS as readonly string[]).includes(name);
}

Try / catch

onSelect={name => {
  try {
    handleIncomeOption(name);
  } catch (e) {
    if (String(e.message).startsWith('Unrecognized menu option')) console.warn('unhandled income option', name);
    else throw e;
  }
}}

Prevention

When it happens

Trigger: Selecting an income-category menu item whose name has no case in the switch — after adding a new item to the items array without a matching case, or dispatching a stale/renamed option name programmatically.

Common situations: Contributors adding an income menu action but only editing items; renamed option ids surviving in saved/UI state; tests clicking items that no longer have a handler.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/0f0f6b04a9b17d9d. Report an issue: GitHub.