moeru-ai/airi · error · Error
Invalid accelerator " ": empty token
Error message
Invalid accelerator "${input}": empty token What it means
Thrown by parseAccelerator in packages/stage-shared/src/global-shortcut/accelerators.ts when splitting the input on '+' produces a segment that trims to an empty string. The parser expects an accelerator shaped like 'Modifier+Key', so stray separators mean it cannot build a valid ShortcutAccelerator. Typical offending inputs are 'Ctrl++A', 'Ctrl++' (a literal '+' key written that way), leading/trailing '+' such as '+Ctrl+A', or double separators from string concatenation bugs.
Solutions
- If the literal '+' key is intended, use the token the key table recognizes for it (e.g. 'Plus') instead of a bare '+', so no empty segment is produced
- Sanitize before parsing: reject or repair inputs that start/end with '+' or contain '++' (e.g. input.replace(/\+\+/g, '+'))
- Validate user input with isValidAccelerator(input) (same module, wraps parseAccelerator in try/catch) before persisting or parsing
- If building accelerators programmatically, filter out empty tokens before joining: parts.filter(Boolean).join('+')
Example fix
// before
const acc = parseAccelerator(userInput) // throws on 'Ctrl++'
// after
import { isValidAccelerator, parseAccelerator } from '../global-shortcut/accelerators'
if (!isValidAccelerator(userInput)) {
throw new Error(`Invalid binding entered: "${userInput}"`)
}
const acc = parseAccelerator(userInput) Defensive patterns
Strategy: validation
Validate before calling
import { isValidAccelerator } from '../global-shortcut/accelerators'
function toAccelerator(input: string) {
if (!isValidAccelerator(input)) return null
return parseAccelerator(input)
} Type guard
const isValidAccelerator = (s: string): boolean => { /* library export; true when parseAccelerator succeeds */ } Try / catch
catch (e) { if (e instanceof Error && e.message.includes('empty token')) return rejectBinding(input); throw e } Prevention
- Validate with isValidAccelerator before persisting user-typed bindings
- Never join accelerator parts without filtering empty tokens
- Represent the literal '+' key with its named token instead of a bare '+'
When it happens
Trigger: Calling parseAccelerator('Ctrl++'), parseAccelerator('+A'), parseAccelerator('Ctrl+Shift+'), or passing a value built by joining tokens where one token is '' (e.g. parts.join('+') with an empty element). Any input where trimmed.split('+') yields an empty segment between two '+' characters or at the ends.
Common situations: Settings UI that lets users type an accelerator free-form and saves it without validation; serializing a keybinding where the '+' key itself was not escaped as 'Plus'; rebuilding an accelerator string from stored modifier/key fields where a field was undefined and became an empty string.
Related errors
- Invalid accelerator " ": duplicate modifier
- Invalid accelerator " ": multiple non-modifier keys (" ", "…
- Invalid accelerator " ": no key token
- Invalid accelerator: empty string
- Invalid accelerator: unknown key
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/9e9e3342de7d4715.
Report an issue: GitHub.
Appendix: 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 677329427f)