moeru-ai/airi · error · Error

Invalid accelerator " ": duplicate modifier

Error message

Invalid accelerator "${input}": duplicate modifier "${modifier}"

What it means

Thrown by parseAccelerator when the same modifier appears twice in one accelerator string. lookupModifierToken maps spelling variants (e.g. 'Ctrl'/'Control', 'Cmd'/'Command', 'CommandOrControl') onto one canonical ShortcutModifier, and the parser rejects repeats of the same canonical modifier even if the spelling differs. Input like 'Ctrl+Control+A' or 'Ctrl+Cmd+Ctrl+K' is therefore invalid.

Solutions

  1. Deduplicate modifier tokens before joining: [...new Set(tokens)].join('+')
  2. When capturing from real keyboard events, accumulate modifiers into a Set keyed by the canonical modifier, not a list of raw tokens
  3. Validate with isValidAccelerator(input) before storing or parsing
  4. When merging configs, let user modifiers replace rather than append to defaults

Example fix

// before
const binding = `${defaultMods}+${userMods}+${key}` // may yield 'Ctrl+Ctrl+K'
const acc = parseAccelerator(binding)

// after
const tokens = [...new Set([...defaultMods.split('+'), ...userMods.split('+')])]
const binding = `${tokens.join('+')}+${key}`
const acc = parseAccelerator(binding)
Defensive patterns

Strategy: validation

Validate before calling

import { isValidAccelerator } from '../global-shortcut/accelerators'

const binding = [...new Set(rawTokens)].join('+')
if (!isValidAccelerator(binding)) return defaultBinding
const acc = parseAccelerator(binding)

Try / catch

catch (e) { if (e instanceof Error && e.message.includes('duplicate modifier')) return promptUserToRebind(); throw e }

Prevention

When it happens

Trigger: Calling parseAccelerator('Ctrl+Ctrl+A'), parseAccelerator('CommandOrControl+Ctrl+X') when both tokens resolve to the same modifier entry, or duplicating a modifier when merging two partial keybinding configurations.

Common situations: Merging a default modifier set with user overrides and concatenating them ('Ctrl' + 'Ctrl+A'); key-capture UI that records a held modifier twice on key repeat; copy-pasting a binding from another OS (e.g. keeping both 'Cmd' and 'CommandOrControl').

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/e2b149cbfcdc1021. Report an issue: GitHub.

Appendix: source

Thrown at packages/stage-shared/src/global-shortcut/accelerators.ts:329

 */
export function parseAccelerator(input: string): ShortcutAccelerator {
  const trimmed = input.trim()
  if (trimmed.length === 0)
    throw new Error('Invalid accelerator: empty string')

  const rawTokens = trimmed.split('+')
  const modifiers: ShortcutModifier[] = []
  let key: ShortcutKey | undefined

  for (const raw of rawTokens) {
    const token = raw.trim()
    if (token.length === 0)
      throw new Error(`Invalid accelerator "${input}": empty token`)

    const modifier = lookupModifierToken(token)
    if (modifier !== undefined) {
      if (modifiers.includes(modifier))
        throw new Error(`Invalid accelerator "${input}": duplicate modifier "${modifier}"`)
      modifiers.push(modifier)
      continue
    }

    if (key !== undefined)
      throw new Error(`Invalid accelerator "${input}": multiple non-modifier keys ("${key}", "${token}")`)
    key = normalizeKeyToken(token)
  }

  if (key === undefined)
    throw new Error(`Invalid accelerator "${input}": no key token`)

  return { modifiers, key }
}

/**
 * Tests whether `input` is a well-formed accelerator string.
 *

View on GitHub (pinned to 677329427f)