{"record":{"id":"88c1557d70bed307","repo":"Hmbown/CodeWhale","slug":"multiple-non-modifier-keys-in-text","errorCode":null,"errorMessage":"multiple non-modifier keys in \"${text}\"","messagePattern":"multiple non-modifier keys in \"(.+?)\"","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/darwin.mjs","lineNumber":492,"sourceCode":"    const lease = await nativeLease(\"key_event\", { code, flags, down: true });\n    try {\n      await action();\n      // The acknowledgement carries the yield_ms the helper waited for a\n      // hardware-input gap before posting the press.\n      return lease.receipt;\n    } finally {\n      await withSignal(null, () => lease.release());\n    }\n  }\n\n  function parseChord(text) {\n    const parts = String(text).split(\"+\").map((s) => s.trim().toLowerCase()).filter(Boolean);\n    if (!parts.length) throw new ExecError(\"empty key text\");\n    let flags = 0;\n    let key = null;\n    for (const p of parts) {\n      if (MODIFIERS[p] != null) flags |= MODIFIERS[p];\n      else if (KEY_CODES[p] != null) { if (key) throw new ExecError(`multiple non-modifier keys in \"${text}\"`); key = p; }\n      else throw new ExecError(`unknown key \"${p}\" (supported: ${Object.keys(KEY_CODES).join(\", \")} + modifiers cmd/ctrl/alt/shift/fn)`);\n    }\n    if (key == null) throw new ExecError(`no non-modifier key in \"${text}\"`);\n    return { flags, code: KEY_CODES[key], key };\n  }\n\n  // ---------- displays ----------\n  async function displayInfo() { return native(\"displays\"); }\n\n  // ---------- screenshots ----------\n  function recordingsDir() {\n    return process.env.CODEWHALE_CU_RECORDINGS_DIR || path.join(os.homedir(), \".codewhale-cu\", \"recordings\");\n  }\n\n  async function screenshot({ display, region, app_ref, window_id, path: outPath } = {}) {\n    // Once an app is selected, ordinary observations follow it behind the\n    // user's work. An explicit display/region remains a deliberate desktop capture.\n    if (app_ref === undefined && display === undefined && region === undefined) app_ref = state.inputApp ?? undefined;","sourceCodeStart":474,"sourceCodeEnd":510,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/darwin.mjs#L474-L510","documentation":"A chord may contain any number of modifiers plus exactly one non-modifier key. parseChord throws when it encounters a second non-modifier key name, because a physical key event can press only one main key at a time.","triggerScenarios":"Passing text like 'cmd+a+b', 'a+b', or a space-separated pair that still splits on '+' into two KEY_CODES names.","commonSituations":"Caller intends 'press a, then b' but formats it as a chord; LLM-generated key strings joining two keys; misunderstanding that '+' sequences mean sequential presses.","solutions":["Send each key press as a separate call: press('a') then press('b')","Keep at most one non-modifier key per chord: 'cmd+c', not 'cmd+c+v'","Normalize/split multi-key input at the call site before invoking the backend"],"exampleFix":"// before\nawait press(\"cmd+c+v\");\n// after\nawait press(\"cmd+c\");\nawait press(\"cmd+v\");","handlingStrategy":"validation","validationCode":"const MODS = new Set([\"cmd\", \"ctrl\", \"alt\", \"shift\", \"fn\"]);\nconst nonMods = key.split(\"+\").map(s => s.trim().toLowerCase()).filter(p => p && !MODS.has(p));\nif (nonMods.length > 1) throw new Error(`at most one non-modifier key, got: ${nonMods.join(\", \")}`);","typeGuard":null,"tryCatchPattern":"try {\n  await press(text);\n} catch (e) {\n  if (e instanceof ExecError && e.message.startsWith(\"multiple non-modifier keys\")) {\n    for (const k of text.split(\"+\")) await press(k.trim());\n  } else throw e;\n}","preventionTips":["Treat '+' as modifier chord syntax only, never as sequencing","Issue one press call per key in sequence","Normalize input in one shared helper before the backend call"],"tags":["keyboard","chord","validation"],"backgroundTag":"invalid-argument-format","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"}