{"record":{"id":"c54a10cb47fd5fd1","repo":"Hmbown/CodeWhale","slug":"unknown-key-combination-text","errorCode":null,"errorMessage":"unknown key combination \"${text}\"","messagePattern":"unknown key combination \"(.+?)\"","errorType":"validation","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/linux.mjs","lineNumber":185,"sourceCode":"\n  function xdotoolKey(text) {\n    return String(text).split(\"+\").map((p) => {\n      const k = p.trim().toLowerCase();\n      if (XKEYS[k]) return XKEYS[k];\n      if (/^f\\d{1,2}$/.test(k)) return k.toUpperCase();\n      return p.trim(); // pass through names already in xdotool form\n    }).join(\"+\");\n  }\n\n  async function waylandKey(text, { repeat = 1, holdMs = 0 } = {}) {\n    need(\"wtype\", \"key presses on Wayland\");\n    const parts = String(text).split(\"+\").map((part) => part.trim().toLowerCase());\n    const aliases = { control: \"ctrl\", meta: \"logo\", cmd: \"logo\", super: \"logo\" };\n    const modifiers = new Set([\"ctrl\", \"alt\", \"shift\", \"logo\", \"win\", \"altgr\", \"capslock\"]);\n    const rawKey = parts.pop();\n    const key = xdotoolKey(rawKey);\n    const mods = parts.map((part) => aliases[part] ?? part);\n    if (!key || mods.some((mod) => !modifiers.has(mod))) throw new ExecError(`unknown key combination \"${text}\"`);\n    const modKey = aliases[rawKey] ?? rawKey;\n    const onlyModifier = modifiers.has(modKey);\n    const args = mods.flatMap((mod) => [\"-M\", mod]);\n    for (let i = 0; i < repeat; i++) {\n      args.push(onlyModifier ? \"-M\" : \"-P\", onlyModifier ? modKey : key);\n      if (holdMs) args.push(\"-s\", String(holdMs));\n      args.push(onlyModifier ? \"-m\" : \"-p\", onlyModifier ? modKey : key);\n    }\n    args.push(...mods.reverse().flatMap((mod) => [\"-m\", mod]));\n    // wtype owns a temporary Wayland keyboard; the compositor releases its\n    // keys on process exit, including cancellation. Keep the complete gesture\n    // in one process (https://github.com/atx/wtype#usage).\n    throwIfAborted();\n    const result = await run(\"wtype\", args, { timeoutMs: Math.max(10_000, holdMs + 8_000) });\n    throwIfAborted();\n    if (result.code !== 0) throw new ExecError(`wtype exited ${result.code}: ${result.stderr.trim().slice(0, 200)}`, result);\n  }\n","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/linux.mjs#L167-L203","documentation":"Thrown by waylandKey when the requested key combination string cannot be mapped to a ydotool/wtype gesture: either the final key fails xdotoolKey() translation (returns falsy) or a modifier is not in the known set {ctrl, alt, shift, logo, win, altgr, capslock}. The library validates the combination up front instead of letting the subprocess fail opaquely.","triggerScenarios":"Calling a key-press action on Wayland with a combination string like 'foo+c', an unrecognized modifier ('command+c' is fine via alias cmd->logo, but 'hyper+c' is not), or an unmappable raw key (empty string, unknown symbol).","commonSituations":"Porting macOS-style shortcut names ('cmd', 'meta') that are aliased — but using names outside the alias table like 'hyper', 'option' (not aliased to alt here); passing an empty key string after splitting on '+'; locale-specific key names.","solutions":["Use only supported modifiers: ctrl, alt, shift, logo/win/super/cmd, altgr, capslock.","Use standard xdotool-style key names for the final key (e.g. 'a', 'Return', 'F5', 'space').","Check the alias table in waylandKey (control->ctrl, meta/cmd/super->logo) and map your app's names to those before calling.","Trim/sanitize the combination string; stray '+' or whitespace yields an empty part.","If you need an unmapped modifier, extend the alias/modifiers sets in the backend or pre-emit the raw key instead."],"exampleFix":"// before\nawait waylandKey('option+c');\n// after\nawait waylandKey('alt+c');","handlingStrategy":"validation","validationCode":"const MODIFIERS = new Set(['ctrl','alt','shift','logo','win','altgr','capslock']);\nconst ALIASES = { control: 'ctrl', meta: 'logo', cmd: 'logo', super: 'logo' };\nfunction isValidCombo(text) {\n  const parts = String(text).split('+').map((p) => p.trim().toLowerCase());\n  if (parts.some((p) => !p)) return false;\n  const rawKey = parts.pop();\n  const mods = parts.map((p) => ALIASES[p] ?? p);\n  return mods.every((m) => MODIFIERS.has(m)) && /^[a-z0-9]+$|^f([1-9]|1[0-2])$|^return$|^space$|^tab$|^escape$/.test(rawKey);\n}","typeGuard":"const isKeyCombo = (v) => typeof v === 'string' && v.trim().length > 0 && isValidCombo(v);","tryCatchPattern":"if (!isKeyCombo(combo)) {\n  throw new Error(`unsupported combination: ${combo}`);\n}\nawait waylandKey(combo); // or wrap in try/catch for ExecError fallback","preventionTips":["Normalize shortcut names through an alias map (cmd/meta/super -> logo, option -> alt) before calling.","Reject empty or double-'+' strings at your boundary.","Keep to xdotool-style key names for the final key.","Validate modifiers against the backend's supported set, not your app's vocabulary.","Unit-test your shortcut table against the backend's alias/modifier sets."],"tags":["validation","keyboard","wayland","key-names"],"backgroundTag":"invalid-argument-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}