Hmbown/CodeWhale · error · ExecError
no non-modifier key in
Error message
no non-modifier key in "${text}" — use hold_key for modifier-only holds What it means
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.
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.
Example fix
// before
await computer.key("cmd");
// after
await computer.hold_key("cmd", "press");
await computer.click({ x: 100, y: 200 });
await computer.hold_key("cmd", "release"); Defensive patterns
Strategy: validation
Validate before calling
const MODS = new Set(["cmd", "ctrl", "alt", "shift", "fn"]);
const parts = chord.split("+").filter(Boolean).map((p) => p.trim().toLowerCase());
const hasMainKey = parts.some((p) => !MODS.has(p));
if (!hasMainKey) throw new Error("chord needs a non-modifier key; use hold_key for modifier-only holds"); Try / catch
try {
await computer.key(chord);
} catch (e) {
if (String(e.message).includes("use hold_key")) {
await computer.hold_key(extractModifier(chord), "press");
// ... perform action ...
await computer.hold_key(extractModifier(chord), "release");
} else throw e;
} Prevention
- 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
When it happens
Trigger: Calling key with 'cmd', 'shift+ctrl', or 'fn' — all parts are modifiers, no main key.
Common situations: 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).
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Choose one input source, an unused --output path (or…
- --days must be an integer from 1 through 90
- duration resolved to zero seconds
- empty key text
- Engine pet clock jump.
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/b68b2457575f299f.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:323
const lease = await nativeLease("key_event", { code, flags, down: true });
try {
return await action();
} 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}" — use hold_key for modifier-only holds`);
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();
fs.mkdirSync(dir, { recursive: true });
// JPEG, not PNG. A screen is photographic content — gradients, wallpaper,View on GitHub (pinned to 433685b202)