actualbudget/actual · error

Unrecognized menu option: ${name}

Error message

Unrecognized menu option: ${name}

What it means

BalanceMenu's onSelect switch handles a fixed set of menu option names (e.g. transfer, cover, carryover) and throws on anything else. It's an exhaustiveness guard: the items array and the switch must stay in sync, and a mismatch is treated as a programming error.

Source

Thrown at packages/desktop-client/src/components/budget/envelope/BalanceMenu.tsx:51

  const balance =
    useEnvelopeSheetValue(envelopeBudget.catBalance(categoryId)) ?? 0;

  return (
    <Menu
      {...props}
      onMenuSelect={name => {
        switch (name) {
          case 'transfer':
            onTransfer?.();
            break;
          case 'carryover':
            onCarryover?.(!carryover);
            break;
          case 'cover':
            onCover?.();
            break;
          default:
            throw new Error(`Unrecognized menu option: ${name}`);
        }
      }}
      items={[
        ...(balance > 0
          ? [
              {
                name: 'transfer',
                text: t('Transfer to another category'),
              },
            ]
          : []),
        ...(balance < 0
          ? [
              {
                name: 'cover',
                text: t('Cover overspending'),
              },
            ]

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Add the missing `case '<name>':` branch to the onSelect switch matching the item you added to the items array.
  2. Type the item name union and annotate onSelect's parameter so TypeScript flags unmatched names at compile time.
  3. If the option is intentionally a no-op for some contexts (e.g. cover without overbudgeted amount), early-return instead of throwing.

Example fix

// before
case 'cover':
  onCover?.();
  break;
default:
  throw new Error(`Unrecognized menu option: ${name}`);

// after
case 'cover':
  onCover?.();
  break;
case 'transfer':
  onTransfer?.();
  break;
default:
  console.warn('Unknown balance menu option', name);
Defensive patterns

Strategy: type-guard

Validate before calling

type BalanceMenuOption = 'transfer' | 'cover' | 'carryover' | 'hold';
function isBalanceMenuOption(name: string): name is BalanceMenuOption {
  return ['transfer', 'cover', 'carryover', 'hold'].includes(name);
}
if (!isBalanceMenuOption(name)) return;

Type guard

function isBalanceMenuOption(name: string): name is BalanceMenuOption {
  return ['transfer', 'cover', 'carryover', 'hold'].includes(name);
}

Try / catch

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

Prevention

When it happens

Trigger: Calling the menu's onSelect with a name that isn't one of the defined items — usually a custom Menu item added by a caller/parent, or a renamed option where the switch wasn't updated.

Common situations: A developer adds an item to the items array but forgets the switch case; a plugin/patched build injects extra options; refactoring item names in one place only.

Related errors


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