{"record":{"id":"b68b2457575f299f","repo":"Hmbown/CodeWhale","slug":"no-non-modifier-key-in-text-use-hold-key-for-modifier-only","errorCode":null,"errorMessage":"no non-modifier key in \"${text}\" — use hold_key for modifier-only holds","messagePattern":"no non-modifier key in \"(.+?)\" — use hold_key for modifier-only holds","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/darwin.mjs","lineNumber":323,"sourceCode":"    const lease = await nativeLease(\"key_event\", { code, flags, down: true });\n    try {\n      return await action();\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}\" — use hold_key for modifier-only holds`);\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;\n    const dir = recordingsDir();\n    fs.mkdirSync(dir, { recursive: true });\n    // JPEG, not PNG. A screen is photographic content — gradients, wallpaper,","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/tui/plugins/computer-use/src/backends/darwin.mjs#L305-L341","documentation":"After parsing, the chord must contain at least one non-modifier key. A string consisting solely of modifiers ('cmd', 'cmd+shift') parses as flags with key==null, which parseChord rejects: modifier-only presses are not key presses here — the dedicated hold_key API covers modifier holds.","triggerScenarios":"Calling key with 'cmd', 'shift+ctrl', or 'fn' — all parts are modifiers, no main key.","commonSituations":"Trying to press a modifier alone to toggle a mode (e.g. sticky keys); building a chord programmatically where the main key variable was empty but modifiers were set; intending to hold cmd while clicking (should use hold_key + click, not key).","solutions":["Add the actual key: 'cmd+c' instead of 'cmd'.","To press/hold a modifier by itself, use hold_key with press and release phases.","To combine a modifier with a mouse action, hold the modifier via hold_key, perform the click, then release.","Guard your wrapper: if all chord tokens are modifiers, route to hold_key instead of key."],"exampleFix":"// before\nawait computer.key(\"cmd\");\n// after\nawait computer.hold_key(\"cmd\", \"press\");\nawait computer.click({ x: 100, y: 200 });\nawait computer.hold_key(\"cmd\", \"release\");","handlingStrategy":"validation","validationCode":"const MODS = new Set([\"cmd\", \"ctrl\", \"alt\", \"shift\", \"fn\"]);\nconst parts = chord.split(\"+\").filter(Boolean).map((p) => p.trim().toLowerCase());\nconst hasMainKey = parts.some((p) => !MODS.has(p));\nif (!hasMainKey) throw new Error(\"chord needs a non-modifier key; use hold_key for modifier-only holds\");","typeGuard":null,"tryCatchPattern":"try {\n  await computer.key(chord);\n} catch (e) {\n  if (String(e.message).includes(\"use hold_key\")) {\n    await computer.hold_key(extractModifier(chord), \"press\");\n    // ... perform action ...\n    await computer.hold_key(extractModifier(chord), \"release\");\n  } else throw e;\n}","preventionTips":["Never call key() with modifiers only — route modifier-only intents to hold_key","Ensure the main key variable in generated chords is always set","When combining modifiers with mouse actions, use hold_key press/release around the click"],"tags":["keyboard","argument-validation","modifier-only"],"backgroundTag":"invalid-argument-format","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}