moeru-ai/airi · error · Error
Invalid accelerator "${input}": duplicate modifier "${modifi
Error message
Invalid accelerator "${input}": duplicate modifier "${modifier}" What it means
While tokenizing, once a token is resolved to a modifier via `lookupModifierToken`, the parser checks `modifiers.includes(modifier)`. If the same modifier was already collected it throws `Invalid accelerator "<input>": duplicate modifier "<modifier>"`. Each modifier (e.g. `cmd-or-ctrl`, `shift`, `alt`, `ctrl`) may appear at most once.
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 27111382b4)
Solutions
- Dedupe modifiers before joining: `[...new Set(mods)]` then `formatAccelerator`.
- Teach the keybind recorder to ignore a modifier key that is already in the active set.
- If accepting aliases, normalize via `lookupModifierToken` first and reject/merge duplicates.
- Use the structured `ShortcutAccelerator` form as the source of truth and only stringify for display/persistence.
Example fix
// before
const accel = `${[...mods, ...defaultMods].join('+')}+${key}`
// after
const uniqMods = [...new Set([...mods, ...defaultMods].map(lookupModifierToken).filter(Boolean))]
const accel = formatAccelerator({ modifiers: uniqMods, key }) Defensive patterns
Strategy: validation
Validate before calling
const mods = [...new Set(rawModifiers.map(m => lookupModifierToken(m)).filter(Boolean))]
const accel = formatAccelerator({ modifiers: mods, key })
parseAccelerator(accel) Type guard
function hasUniqueModifiers(input: string): boolean {
const seen = new Set<string>()
for (const tok of input.split('+').map(t => t.trim())) {
const m = lookupModifierToken(tok)
if (m === undefined) continue
if (seen.has(m)) return false
seen.add(m)
}
return true
} Try / catch
try {
parseAccelerator(raw)
} catch (e) {
if (e instanceof Error && e.message.includes('duplicate modifier')) {
// dedupe and retry, or prompt user
} else throw e
} Prevention
- Normalize aliases via lookupModifierToken and dedupe before serializing.
- Use the structured ShortcutAccelerator as source of truth.
- Have the recorder ignore modifier keys already in the active set.
When it happens
Trigger: Inputs such as `"Ctrl+Ctrl+K"`, `"Mod+Mod+Shift+A"`, `"Shift+Shift+Shift+X"`, or aliases that resolve to the same canonical modifier (`"Cmd+CmdOrCtrl+K"` where both map to `cmd-or-ctrl`).
Common situations: Combining user-entered modifiers with a default modifier set without deduping; alias confusion where `Cmd`, `CmdOrCtrl`, `Mod`, `Super`, and `Win` all normalize to one canonical token; config migration that appended a modifier twice.
Related errors
- Invalid accelerator: empty string
- Invalid accelerator "${input}": empty token
- Invalid accelerator "${input}": multiple non-modifier keys (
- Invalid accelerator "${input}": no key token
- Invalid accelerator: unknown key "${token}"
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/e2b149cbfcdc1021.
Report an issue: GitHub.