Hmbown/CodeWhale · error · ExecError

unknown key " " (supported: + modifiers…

Error message

unknown key "${p}" (supported: ${Object.keys(KEY_CODES).join(", ")} + modifiers cmd/ctrl/alt/shift/fn)

What it means

Every '+'-separated token must be either a known modifier (cmd/ctrl/alt/shift/fn) or a key in the backend's KEY_CODES table. Any unrecognized token is rejected, and the message enumerates the supported key names so the caller can correct it.

Solutions

  1. Use a key name from the supported list printed in the error message
  2. Map platform aliases before calling: 'win'/'meta' -> 'cmd', 'esc' -> 'escape' if that is the table's name
  3. Extend/consult the KEY_CODES table for the exact accepted names
  4. Trim and lowercase input (parseChord already lowercases, so fix spelling rather than case)

Example fix

// before
await press("win+c");
// after
await press("cmd+c");
Defensive patterns

Strategy: validation

Validate before calling

const ALIAS = { win: "cmd", meta: "cmd", super: "cmd", esc: "escape", return: "enter" };
const normalized = key.split("+").map(p => ALIAS[p.trim().toLowerCase()] ?? p.trim().toLowerCase()).join("+");

Try / catch

try {
  await press(key);
} catch (e) {
  if (e instanceof ExecError && e.message.startsWith("unknown key")) {
    const supported = e.message.match(/supported: ([^+]+)/)?.[1];
    throw new Error(`key '${key}' unsupported; use one of: ${supported}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing key names not in KEY_CODES, e.g. 'win', 'meta', 'return' (if table uses 'enter'), 'spacebar', 'esc' vs 'escape', uppercase (input is lowercased, so this is safe), or accented/symbol characters.

Common situations: Cross-platform key naming habits (Windows key names on macOS backend); typos like 'cmnd'; assuming alias coverage the table does not have.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/63631cb87bf04f4c. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:493

    try {
      await action();
      // The acknowledgement carries the yield_ms the helper waited for a
      // hardware-input gap before posting the press.
      return lease.receipt;
    } finally {
      await withSignal(null, () => lease.release());
    }
  }

  function parseChord(text) {
    const parts = String(text).split("+").map((s) => s.trim().toLowerCase()).filter(Boolean);
    if (!parts.length) throw new ExecError("empty key text");
    let flags = 0;
    let key = null;
    for (const p of parts) {
      if (MODIFIERS[p] != null) flags |= MODIFIERS[p];
      else if (KEY_CODES[p] != null) { if (key) throw new ExecError(`multiple non-modifier keys in "${text}"`); key = p; }
      else throw new ExecError(`unknown key "${p}" (supported: ${Object.keys(KEY_CODES).join(", ")} + modifiers cmd/ctrl/alt/shift/fn)`);
    }
    if (key == null) throw new ExecError(`no non-modifier key in "${text}"`);
    return { flags, code: KEY_CODES[key], key };
  }

  // ---------- displays ----------
  async function displayInfo() { return native("displays"); }

  // ---------- screenshots ----------
  function recordingsDir() {
    return process.env.CODEWHALE_CU_RECORDINGS_DIR || path.join(os.homedir(), ".codewhale-cu", "recordings");
  }

  async function screenshot({ display, region, app_ref, window_id, path: outPath } = {}) {
    // Once an app is selected, ordinary observations follow it behind the
    // user's work. An explicit display/region remains a deliberate desktop capture.
    if (app_ref === undefined && display === undefined && region === undefined) app_ref = state.inputApp ?? undefined;
    const dir = recordingsDir();

View on GitHub (pinned to 73e0f67d83)