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

  1. Read the embedded stderr/stdout snippet for the device-side cause and fix it (unlock the screen, free storage, etc.).
  2. Run `hdc list targets` to confirm the device is connected and authorized; restart with `hdc kill` / reconnect.
  3. Retry the operation — transient device/daemon hiccups commonly cause one-off non-zero exits.
  4. 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

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


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)