Hmbown/CodeWhale · error · ExecError
hdc shell exited
Error message
hdc shell ${args[0]} exited ${r.code}: ${(r.stderr || r.stdout).trim().slice(0, 300)} What it means
deviceOut wraps `hdc shell` invocations and throws this ExecError when the hdc command exits non-zero, embedding the command name, exit code, and up to 300 chars of stderr/stdout. It surfaces device-side command failures from the HarmonyOS computer-use backend.
Solutions
- Read the embedded stderr/stdout snippet for the device-side cause and fix it (unlock the screen, free storage, etc.).
- Run `hdc list targets` to confirm the device is connected and authorized; restart with `hdc kill` / reconnect.
- Retry the operation — transient device/daemon hiccups commonly cause one-off non-zero exits.
- Catch the ExecError and treat it as a backend failure, falling back to re-observation.
Example fix
try {
const out = await backend.snapshot('/tmp/shot.jpeg');
} catch (e) {
// e.message e.g. 'hdc shell snapshot_display exited 1: ...'
console.error('device command failed:', e.message);
await hdcReconnect(); // hdc kill; hdc list targets
} Defensive patterns
Strategy: try-catch
Validate before calling
const targets = await exec.shell(['list', 'targets']); // confirm a connected device before issuing shell commands
Type guard
null
Try / catch
try { const out = await deviceOut(['snapshot_display', '-f', remote]); } catch (e) { if (String(e.message).startsWith('hdc shell')) { log(e.message); await reconnectHdc(); /* retry once */ } else throw e; } Prevention
- Check `hdc list targets` before batch operations.
- Unlock the device screen before snapshot/uitest commands.
- Parse the embedded stderr snippet rather than treating all failures alike.
- Add retries with backoff for transient device errors.
When it happens
Trigger: Any hdc shell call whose process exits non-zero — e.g. `hdc shell snapshot_display -f <path>` failing when the device is locked, `bm dump -a` when the bundle manager errors, or uitest commands failing on an unexpected UI state.
Common situations: Device disconnected or unauthorized (hdc target offline), screen locked so snapshot_display fails, DEVICE_TMP not writable, HarmonyOS build lacking a subcommand, or a stale hdc daemon.
Related errors
- aa start failed
- hdc file recv failed
- invalid_target
- clipboard read is not exposed by hdc on current HarmonyOS…
- clipboard write is not exposed by hdc on current HarmonyOS…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/8d2e3ea62c3d8622.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/harmonyos.mjs:75
}
/** Coordinate clicks on this backend are always raw pointer events; strategy="a11y" must fail closed rather than silently degrade. */
function assertEventStrategy(strategy) {
if (strategy != null && strategy !== "auto" && strategy !== "event") {
throw new ExecError(`strategy "${strategy}" is macOS-only; this backend dispatches coordinate clicks as raw pointer events — use an element target for a semantic action`);
}
}
function rejectAppSelectors(args) {
if (["app_ref", "window_id", "windowIndex"].some(key => Object.hasOwn(args, key))) throw Object.assign(new ExecError("HarmonyOS cannot select an app or window for observation or element actions; explicit selectors are unsupported"), { code: "unsupported_selector" });
}
export function create({ exec }) {
const shell = (args, opts = {}) => exec.shell(args, { timeoutMs: 20_000, ...opts });
async function deviceOut(args, opts = {}) {
const r = await shell(args, opts);
if (r.code !== 0) throw new ExecError(`hdc shell ${args[0]} exited ${r.code}: ${(r.stderr || r.stdout).trim().slice(0, 300)}`, r);
return r.stdout;
}
async function snapshot(localPath) {
const remote = `${DEVICE_TMP}-${crypto.randomBytes(3).toString("hex")}.jpeg`;
await deviceOut(["snapshot_display", "-f", remote], { timeoutMs: 25_000 });
try {
await exec.pullFile(remote, localPath, { timeoutMs: 30_000 });
} finally {
await shell(["rm", "-f", remote]).catch(() => {});
}
return localPath;
}
async function dumpLayout() {
const remote = `${DEVICE_TMP}-layout.json`;
await deviceOut(["uitest", "dumpLayout", "-p", remote], { timeoutMs: 40_000 });
let data;View on GitHub (pinned to 73e0f67d83)