Hmbown/CodeWhale · error · ExecError

empty key text

Error message

empty key text

What it means

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.

Solutions

  1. Pass a non-empty key name, e.g. 'a', 'enter', 'cmd+c'
  2. Coalesce empty input before the call and skip the key action when blank
  3. Fix the upstream source that produced the empty string (missing model field, failed lookup)
  4. Validate required fields at the tool-argument layer before reaching the backend

Example fix

// before
await press(opts.key ?? "");
// after
if (!opts.key) throw new Error("key is required");
await press(opts.key);
Defensive patterns

Strategy: validation

Validate before calling

if (typeof key !== "string" || !key.trim()) throw new Error("key is required and must be non-empty");

Type guard

const isNonEmptyKey = (k) => typeof k === "string" && k.trim().length > 0;

Try / catch

try {
  await press(key);
} catch (e) {
  if (e instanceof ExecError && e.message === "empty key text") {
    // treat as missing required field; report to the caller/model
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: Model output omitted the key field; config placeholder left blank; a variable interpolating an empty string because a lookup failed upstream.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

    return { action_sent: true, strategy: "event", at: { x, y }, button, clicks, ...pointerCost(r),
             ...(a11yReason ? { a11y_reason: a11yReason } : {}) };
  }

  async function withPressedKey(code, flags, action) {
    const lease = await nativeLease("key_event", { code, flags, down: true });
    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");
  }

View on GitHub (pinned to 73e0f67d83)