moeru-ai/airi · error · Error

Invalid accelerator "${input}": no key token

Error message

Invalid accelerator "${input}": no key token

What it means

After the token loop finishes, if `key` is still `undefined` (no non-modifier token was found), the parser throws `Invalid accelerator "<input>": no key token`. An accelerator must resolve to exactly one non-modifier key; a modifiers-only string is invalid.

Source

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

    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
 */
export function isValidAccelerator(input: string): boolean {
  try {
    parseAccelerator(input)
    return true
  }

View on GitHub (pinned to 27111382b4)

Solutions

  1. Require a non-modifier key in the recorder before enabling save.
  2. Default unbound shortcuts to `null`, not a modifiers-only placeholder string.
  3. Run `isValidAccelerator(input)` and prompt the user to complete the combo.
  4. If building the string programmatically, assert a `key` is set before calling `formatAccelerator`/round-tripping.

Example fix

// before
const accel = mods.length ? `${mods.join('+')}` : ''
parseAccelerator(accel)

// after
if (!key) return  // do not persist a modifiers-only combo
const accel = formatAccelerator({ modifiers: mods, key })
Defensive patterns

Strategy: validation

Validate before calling

const hasKey = raw.split('+').map(t => t.trim()).some(t => lookupModifierToken(t) === undefined && t.length > 0)
if (hasKey) parseAccelerator(raw)

Type guard

function acceleratorHasKeyToken(input: string): boolean {
  return input.split('+').map(t => t.trim()).some(t => lookupModifierToken(t) === undefined && t.length > 0)
}

Try / catch

try {
  parseAccelerator(raw)
} catch (e) {
  if (e instanceof Error && e.message.includes('no key token')) {
    // prompt user to complete the combo
  } else throw e
}

Prevention

When it happens

Trigger: Inputs such as `"Ctrl"`, `"Mod+Shift"`, `"Alt+Ctrl+Shift"`, `"Cmd"`, or any string where every token is a modifier alias.

Common situations: A keybind recorder that only captured modifier keydowns and never the final character; a partial config; user releasing the key before the recorder closed the combo; a default value that lists only modifiers as a placeholder.

Related errors


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