moeru-ai/airi · error · Error

Invalid accelerator "${input}": empty token

Error message

Invalid accelerator "${input}": empty token

What it means

`parseAccelerator` splits the trimmed input on `+` and trims each token. If any resulting token has length 0, it throws `Invalid accelerator "<input>": empty token`. This means the string contains a `+` with nothing (or only whitespace) on one side of it. The original `input` is echoed in the message so the malformed region is visible.

Source

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

 *   // => { modifiers: ['cmd-or-ctrl', 'shift'], key: 'KeyK' }
 *
 * @example
 *   parseAccelerator(' CmdOrCtrl + Shift + KeyK ')
 *   // => same as above; whitespace tolerated
 */
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 }

View on GitHub (pinned to 27111382b4)

Solutions

  1. Sanitize the string before parsing: collapse repeated `+` and strip leading/trailing `+` (`input.replace(/\+{2,}/g, '+').replace(^\+|\+$/g, '').trim()`).
  2. Use `isValidAccelerator(input)` to gate the parse on user-supplied values.
  3. Reconstruct the accelerator programmatically from a structured `{ modifiers, key }` via `formatAccelerator` instead of hand-concatenating strings.
  4. Inspect the echoed `<input>` in the message to locate the stray separator.

Example fix

// before
const acc = parseAccelerator(rawUserInput)

// after
const clean = rawUserInput.replace(/\+{2,}/g, '+').replace(/^\+|\+$/g, '').trim()
if (!clean) return
const acc = parseAccelerator(clean)
Defensive patterns

Strategy: validation

Validate before calling

function sanitizeAccelerator(input: string): string {
  return input.replace(/\+{2,}/g, '+').replace(/^\+|\+$/g, '').trim()
}

const clean = sanitizeAccelerator(raw)
if (clean) parseAccelerator(clean)

Type guard

function isWellFormedAccelerator(input: string): boolean {
  const trimmed = input.trim()
  if (!trimmed) return false
  return trimmed.split('+').every(tok => tok.trim().length > 0)
}

Try / catch

try {
  parseAccelerator(raw)
} catch (e) {
  if (e instanceof Error && e.message.includes('empty token')) {
    // re-prompt the user for the keybind
  } else throw e
}

Prevention

When it happens

Trigger: Inputs such as `"Ctrl++K"`, `"+K"`, `"K+"`, `"Ctrl+ +K"` (whitespace-only segment), `"Mod+"`, or any value where `trimmed.split('+')` yields an empty/whitespace element after per-token `.trim()`.

Common situations: Typos in hand-authored keybind config (`"Mod + + K"`); concatenating modifiers with a stray separator (`[mod, '', key].join('+')`); copy-paste from docs that used an em-dash or special plus character; a keystroke recorder that emits an empty segment between two key events.

Related errors


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