{"record":{"id":"3c3c47ea4413f23e","repo":"moeru-ai/airi","slug":"invalid-accelerator-input-no-key-token","errorCode":null,"errorMessage":"Invalid accelerator \"${input}\": no key token","messagePattern":"Invalid accelerator \"(.+?)\": no key token","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-shared/src/global-shortcut/accelerators.ts","lineNumber":340,"sourceCode":"    const token = raw.trim()\n    if (token.length === 0)\n      throw new Error(`Invalid accelerator \"${input}\": empty token`)\n\n    const modifier = lookupModifierToken(token)\n    if (modifier !== undefined) {\n      if (modifiers.includes(modifier))\n        throw new Error(`Invalid accelerator \"${input}\": duplicate modifier \"${modifier}\"`)\n      modifiers.push(modifier)\n      continue\n    }\n\n    if (key !== undefined)\n      throw new Error(`Invalid accelerator \"${input}\": multiple non-modifier keys (\"${key}\", \"${token}\")`)\n    key = normalizeKeyToken(token)\n  }\n\n  if (key === undefined)\n    throw new Error(`Invalid accelerator \"${input}\": no key token`)\n\n  return { modifiers, key }\n}\n\n/**\n * Tests whether `input` is a well-formed accelerator string.\n *\n * Use when:\n * - Gating user input without needing the parsed result\n *\n * Returns:\n * - `true` when `parseAccelerator` would succeed, `false` otherwise\n */\nexport function isValidAccelerator(input: string): boolean {\n  try {\n    parseAccelerator(input)\n    return true\n  }","sourceCodeStart":322,"sourceCodeEnd":358,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-shared/src/global-shortcut/accelerators.ts#L322-L358","documentation":"Thrown by parseAccelerator when the input contains only modifiers (or nothing but modifier aliases) and no main key token. After the token loop, `key` is still undefined, so strings like 'Ctrl', 'Ctrl+Shift', or '' after trimming of key parts cannot produce a ShortcutAccelerator. Every accelerator must terminate in exactly one non-modifier key.","triggerScenarios":"Calling parseAccelerator('Ctrl+Shift'), parseAccelerator('Alt'), or passing a stored binding whose key field was lost (e.g. JSON migration dropped the `key` property and it serialized as 'Ctrl+undefined' trimmed to modifiers only).","commonSituations":"Key-capture UI where the user releases keys before pressing a main key and only modifier keydowns were recorded; serializing an accelerator object back to a string but omitting acc.key; partially migrated settings files from an older keybinding format.","solutions":["In capture UI, only commit a binding once a non-modifier key is pressed; ignore modifier-only states","When serializing, always include the key: `${acc.modifiers.join('+')}+${acc.key}`","Validate stored settings at load time with isValidAccelerator and fall back to defaults for invalid entries","Treat an accelerator object with a falsy key as corrupt data and re-prompt the user"],"exampleFix":"// before\nconst acc = parseAccelerator(capturedBinding) // capturedBinding === 'Ctrl+Shift'\n\n// after\n// only commit when a main key exists\nif (!captured.key) {\n  // keep listening; do not parse a modifier-only string\n} else {\n  const acc = parseAccelerator(`${[...captured.modifiers, captured.key].join('+')}`)\n}","handlingStrategy":"validation","validationCode":"import { isValidAccelerator } from '../global-shortcut/accelerators'\n\nif (!isValidAccelerator(stored)) {\n  settings.binding = DEFAULT_BINDING // quarantine corrupt entry\n}","typeGuard":"function hasKeyToken(s: string): boolean {\n  // last '+'-separated segment must be a non-modifier; simplest: reuse the library predicate\n  return isValidAccelerator(s)\n}","tryCatchPattern":"catch (e) { if (e instanceof Error && e.message.includes('no key token')) return DEFAULT_BINDING; throw e }","preventionTips":["In capture UI, commit a binding only after a non-modifier key press","Always serialize as `${modifiers.join('+')}+${key}`","Validate stored settings at startup and reset invalid bindings to defaults"],"tags":["keyboard-shortcut","accelerator","missing-key","input-validation"],"backgroundTag":"invalid-accelerator-string","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}