moeru-ai/airi · error · Error

Invalid accelerator "${input}": multiple non-modifier keys (

Error message

Invalid accelerator "${input}": multiple non-modifier keys ("${key}", "${token}")

What it means

The parser allows exactly one non-modifier key token. The first non-modifier token is stored in `key`; if a second non-modifier token is encountered (i.e. `key !== undefined`), it throws `Invalid accelerator "<input>": multiple non-modifier keys ("<key>", "<token>")`. Both offending tokens are included in the message.

Source

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

  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.
 *
 * Use when:
 * - Gating user input without needing the parsed result
 *
 * Returns:
 * - `true` when `parseAccelerator` would succeed, `false` otherwise
 */

View on GitHub (pinned to 27111382b4)

Solutions

  1. If you need a key sequence, store two separate `ShortcutAccelerator` values, not one string.
  2. Validate single-key intent in the recorder before serialization.
  3. Run `isValidAccelerator(input)` on user input and surface a friendly message.
  4. Inspect the two tokens in the error to find which key was extra.

Example fix

// before
parseAccelerator('Ctrl+A+B')  // throws

// after
// represent the sequence as two bindings
const combo1 = parseAccelerator('Ctrl+A')
const combo2 = parseAccelerator('Ctrl+B')
Defensive patterns

Strategy: validation

Validate before calling

function countNonModifierTokens(input: string): number {
  return input.split('+').map(t => t.trim()).filter(t => lookupModifierToken(t) === undefined && t.length > 0).length
}

if (countNonModifierTokens(raw) === 1) parseAccelerator(raw)

Type guard

function hasSingleKey(input: string): boolean {
  return countNonModifierTokens(input) === 1
}

Try / catch

try {
  parseAccelerator(raw)
} catch (e) {
  if (e instanceof Error && e.message.includes('multiple non-modifier keys')) {
    // split into two separate bindings
  } else throw e
}

Prevention

When it happens

Trigger: Inputs such as `"Ctrl+A+B"`, `"KeyK+KeyL"`, `"Shift+F1+F2"`, or any string where two tokens survive `lookupModifierToken` as non-modifiers. Also `"A+B"` with no modifiers.

Common situations: A chord/sequence UI that tried to express a multi-keystroke shortcut as one accelerator (accelerators are single combos, not sequences); user typing a second character into a single-key field; merging two shortcuts accidentally.

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/eae784308a09ffc5. Report an issue: GitHub.