moeru-ai/airi · error · Error
Invalid accelerator " ": duplicate modifier
Error message
Invalid accelerator "${input}": duplicate modifier "${modifier}" What it means
Thrown by parseAccelerator when the same modifier appears twice in one accelerator string. lookupModifierToken maps spelling variants (e.g. 'Ctrl'/'Control', 'Cmd'/'Command', 'CommandOrControl') onto one canonical ShortcutModifier, and the parser rejects repeats of the same canonical modifier even if the spelling differs. Input like 'Ctrl+Control+A' or 'Ctrl+Cmd+Ctrl+K' is therefore invalid.
Solutions
- Deduplicate modifier tokens before joining: [...new Set(tokens)].join('+')
- When capturing from real keyboard events, accumulate modifiers into a Set keyed by the canonical modifier, not a list of raw tokens
- Validate with isValidAccelerator(input) before storing or parsing
- When merging configs, let user modifiers replace rather than append to defaults
Example fix
// before
const binding = `${defaultMods}+${userMods}+${key}` // may yield 'Ctrl+Ctrl+K'
const acc = parseAccelerator(binding)
// after
const tokens = [...new Set([...defaultMods.split('+'), ...userMods.split('+')])]
const binding = `${tokens.join('+')}+${key}`
const acc = parseAccelerator(binding) Defensive patterns
Strategy: validation
Validate before calling
import { isValidAccelerator } from '../global-shortcut/accelerators'
const binding = [...new Set(rawTokens)].join('+')
if (!isValidAccelerator(binding)) return defaultBinding
const acc = parseAccelerator(binding) Try / catch
catch (e) { if (e instanceof Error && e.message.includes('duplicate modifier')) return promptUserToRebind(); throw e } Prevention
- Accumulate modifiers in a Set keyed by canonical modifier during key capture
- Let user modifier overrides replace defaults instead of concatenating
- Run settings-load validation and fall back to defaults for invalid entries
When it happens
Trigger: Calling parseAccelerator('Ctrl+Ctrl+A'), parseAccelerator('CommandOrControl+Ctrl+X') when both tokens resolve to the same modifier entry, or duplicating a modifier when merging two partial keybinding configurations.
Common situations: Merging a default modifier set with user overrides and concatenating them ('Ctrl' + 'Ctrl+A'); key-capture UI that records a held modifier twice on key repeat; copy-pasting a binding from another OS (e.g. keeping both 'Cmd' and 'CommandOrControl').
Related errors
- Invalid accelerator " ": empty token
- 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/e2b149cbfcdc1021.
Report an issue: GitHub.
Appendix: source
Thrown at packages/stage-shared/src/global-shortcut/accelerators.ts:329
*/
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 }
}
/**
* Tests whether `input` is a well-formed accelerator string.
*View on GitHub (pinned to 677329427f)