Hmbown/CodeWhale · error · ExecError
uitest uiInput has no right-click; use longClick semantics…
Error message
uitest uiInput has no right-click; use longClick semantics via hold or left_click
What it means
The harmony backend maps input injection to uitest's `uiInput` shell command, which has no right-click sub-command (touch devices have no secondary button). `right_click` therefore always throws this ExecError. The message points at the available substitute: longClick semantics via hold_key({text:"click"}) or a plain left_click.
Solutions
- Replace the right-click with longClick semantics: perform_action({ target, action: "longClick" }) or hold_key({ text: "click" }) — this is the touch-device idiom for a context menu.
- If a long press does not open the desired menu, use left_click instead and drive the UI through whatever on-screen affordance exists.
- Guard in the caller: branch on backend type (or a capabilities flag) and rewrite right_click to longClick before dispatching.
- If the device build ever gains right-click input, implement it in harmonyos.mjs's right_click slot; until then treat it as unsupported.
Example fix
// before
await backend.right_click({ target });
// after
// long-press is the touch equivalent of right-click
await backend.perform_action({ target, action: "longClick" }); Defensive patterns
Strategy: try-catch
Validate before calling
const isTouchBackend = (backend) => backend.kind === "harmonyos";
if (isTouchBackend(backend)) {
await backend.perform_action({ target, action: "longClick" });
} else {
await backend.right_click({ target });
} Type guard
const supportsRightClick = (backend) => backend.kind !== "harmonyos";
Try / catch
try {
await backend.right_click({ target });
} catch (e) {
if (/uitest uiInput has no right-click/.test(e.message)) {
await backend.perform_action({ target, action: "longClick" });
} else {
throw e;
}
} Prevention
- Treat right_click as a desktop-only action; map it to longClick for touch backends in the action-dispatch layer.
- Author agent prompts/plans with backend-aware action vocabularies.
- Add a smoke test per backend that enumerates every action so unsupported slots surface in CI, not in the field.
- Prefer composite gestures (longClick, swipe) when writing portable plans.
When it happens
Trigger: Calling the right_click action ({ action: "right_click", target }) on a backend created in harmonyos.mjs; an agent plan written for desktop that includes context-menu right-clicks run against a HarmonyOS device. Unconditional — target coordinates are never examined.
Common situations: Desktop-oriented automation scripts that open context menus; a shared action vocabulary executed across multiple backends where right_click is valid on some but not harmonyos; ported test suites that assume mouse semantics.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- middle click is not exposed by uitest uiInput
- low-level press/release is not exposed by uitest uiInput…
- zoom is not supported on the harmony backend yet —…
- aa start failed
- app_denied
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/8285e64ec41bbde8.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/harmonyos.mjs:224
const { path: outPath } = args;
const dir = process.env.CODEWHALE_CU_RECORDINGS_DIR || path.join(os.homedir(), ".codewhale-cu", "recordings");
fs.mkdirSync(dir, { recursive: true });
const file = outPath || path.join(dir, `shot-${new Date().toISOString().replace(/[:.]/g, "-")}-${crypto.randomBytes(3).toString("hex")}.jpeg`);
await snapshot(file);
const buf = fs.readFileSync(file);
displayPixels = jpegSize(buf) ?? displayPixels;
return { file, bytes: buf.length, pixels: jpegSize(buf), scale: 1, points: jpegSize(buf) };
},
zoom: async ({ region, path: outPath }) => {
throw new ExecError("zoom is not supported on the harmony backend yet — screenshot + region on the host is the workaround");
},
left_click: ({ target, strategy }) => { assertEventStrategy(strategy); return uiInput(["click", String(Math.round(target.x)), String(Math.round(target.y))]); },
double_click: ({ target }) => uiInput(["doubleClick", String(Math.round(target.x)), String(Math.round(target.y))]),
triple_click: async ({ target }) => {
await uiInput(["doubleClick", String(Math.round(target.x)), String(Math.round(target.y))]);
return uiInput(["click", String(Math.round(target.x)), String(Math.round(target.y))]);
},
right_click: async () => { throw new ExecError("uitest uiInput has no right-click; use longClick semantics via hold or left_click"); },
middle_click: async () => { throw new ExecError("middle click is not exposed by uitest uiInput"); },
mouse_move: async () => ({ action_sent: false, note: "hover without press is not exposed by uitest uiInput" }),
left_click_drag: ({ from_target: from, to }) =>
uiInput(["swipe", String(Math.round(from.x)), String(Math.round(from.y)), String(Math.round(to.x)), String(Math.round(to.y)), "200"], { timeoutMs: 30_000 }),
left_mouse_down: async () => { throw new ExecError("low-level press/release is not exposed by uitest uiInput; use left_click_drag"); },
left_mouse_up: async () => { throw new ExecError("low-level press/release is not exposed by uitest uiInput; use left_click_drag"); },
scroll: ({ target, direction = "down", amount = 300 }) => {
const dist = Math.max(60, Math.min(1200, amount * 24));
const dx = direction === "left" ? dist : direction === "right" ? -dist : 0;
const dy = direction === "up" ? dist : direction === "down" ? -dist : 0;
return uiInput(["swipe", String(Math.round(target.x)), String(Math.round(target.y)), String(Math.round(target.x + dx)), String(Math.round(target.y + dy)), "400"]);
},
type: async ({ text }) => {
if (!text) return { action_sent: false, note: "empty text" };
await uiInput(["inputText", "300", "300", escDeviceText(text)]).catch(async (e) => {
// Some builds require coordinates of the focused field; retry with a click-first pattern.
throw e;
});View on GitHub (pinned to 73e0f67d83)