{"record":{"id":"043d1d8257ec81cb","repo":"Hmbown/CodeWhale","slug":"empty-key-text","errorCode":null,"errorMessage":"empty key text","messagePattern":"empty key text","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/darwin.mjs","lineNumber":487,"sourceCode":"    return { action_sent: true, strategy: \"event\", at: { x, y }, button, clicks, ...pointerCost(r),\n             ...(a11yReason ? { a11y_reason: a11yReason } : {}) };\n  }\n\n  async function withPressedKey(code, flags, action) {\n    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  }","sourceCodeStart":469,"sourceCodeEnd":505,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/darwin.mjs#L469-L505","documentation":"parseChord splits key text on '+' and requires at least one part after trimming and filtering empties. An empty or whitespace-only key string (or text like '+' that yields no non-empty parts) cannot name any key, so the backend throws rather than synthesizing a meaningless event.","triggerScenarios":"Calling a key-press API with key='' , key='   ', key='+', or a value that is null/undefined coerced to 'null' handled elsewhere — anything that produces zero non-empty parts.","commonSituations":"Model output omitted the key field; config placeholder left blank; a variable interpolating an empty string because a lookup failed upstream.","solutions":["Pass a non-empty key name, e.g. 'a', 'enter', 'cmd+c'","Coalesce empty input before the call and skip the key action when blank","Fix the upstream source that produced the empty string (missing model field, failed lookup)","Validate required fields at the tool-argument layer before reaching the backend"],"exampleFix":"// before\nawait press(opts.key ?? \"\");\n// after\nif (!opts.key) throw new Error(\"key is required\");\nawait press(opts.key);","handlingStrategy":"validation","validationCode":"if (typeof key !== \"string\" || !key.trim()) throw new Error(\"key is required and must be non-empty\");","typeGuard":"const isNonEmptyKey = (k) => typeof k === \"string\" && k.trim().length > 0;","tryCatchPattern":"try {\n  await press(key);\n} catch (e) {\n  if (e instanceof ExecError && e.message === \"empty key text\") {\n    // treat as missing required field; report to the caller/model\n  } else throw e;\n}","preventionTips":["Make key a required field in your tool schema","Coalesce-and-skip: skip the action (with a log) when the key is blank","Check upstream lookups that interpolate into the key argument"],"tags":["keyboard","validation","empty-string"],"backgroundTag":"empty-required-field","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"}