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
- 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
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
- 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
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
- mode must not be empty
- model must not be empty
- multiple non-modifier keys in
- no non-modifier key in
- permission_posture must not be empty
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)