moeru-ai/airi · error · Error

Invalid accelerator " ": multiple non-modifier keys (" ", "…

Error message

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

What it means

Thrown by parseAccelerator when more than one non-modifier key token is present. An accelerator must end with exactly one main key; the parser records the first non-modifier token in `key` and throws as soon as a second one appears. Input like 'Ctrl+A+B' or 'Shift+F1+F2' is rejected.

Solutions

  1. Split multi-key chords into separate accelerators and handle sequencing yourself; each accelerator string keeps exactly one key token
  2. If concatenating strings, ensure only the final segment is a key — treat earlier segments as modifiers only
  3. Validate with isValidAccelerator(input) before saving user bindings
  4. In capture UI, reset the recorded key slot on each new non-modifier keydown

Example fix

// before
parseAccelerator('Ctrl+K Ctrl+S')   // second token after space also fails
parseAccelerator('Ctrl+K+S')         // throws: multiple non-modifier keys

// after
// model chords as an ordered list of accelerators
const chord = [parseAccelerator('Ctrl+K'), parseAccelerator('Ctrl+S')]
Defensive patterns

Strategy: validation

Validate before calling

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

if (!isValidAccelerator(candidate)) {
  // reject or treat as chord sequence, e.g. split on space and validate each part
  const parts = candidate.split(/\s+/)
  if (!parts.every(isValidAccelerator)) return null
}

Try / catch

catch (e) { if (e instanceof Error && e.message.includes('multiple non-modifier keys')) return splitIntoChord(input); throw e }

Prevention

When it happens

Trigger: Calling parseAccelerator('Ctrl+A+B'), parseAccelerator('A B'), or passing a chord/sequence ('Ctrl+K Ctrl+S' style) where two key tokens end up joined with '+' instead of separated as a sequence.

Common situations: Trying to express VS Code-style key chords in a single accelerator string; concatenating a previously parsed key back onto a new binding ('Ctrl+' + 'A' + 'B'); capture UI that records two taps of letter keys without clearing the key slot between them.

Related errors


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

Appendix: 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 677329427f)