Hmbown/CodeWhale · error · ExecError

hdc file recv failed

Error message

hdc file recv failed: ${r.stderr.trim().slice(0, 300)}

What it means

pullFile() on the HDC (HarmonyOS) transport copies a file from the device with `hdc file recv`; a nonzero exit throws this ExecError embedding trimmed stderr (max 300 chars). Before running, the remote path is stripped of a leading '/' and validated by safeRemotePath to block traversal/metacharacters, so this error is about the hdc transfer itself failing.

Solutions

  1. Verify device connectivity: hdc list targets shows the expected device
  2. Confirm the file exists on device: hdc shell ls <remotePath>
  3. Check the local destination directory exists and is writable
  4. Read embedded stderr for the specific hdc failure (path, permission, connection)
  5. Ensure the correct device target is selected in targetArgs if multiple devices are attached

Example fix

// before
await ex.pullFile("/data/log/app.log", "/tmp/app.log"); // file missing on device
// after
const ls = await run("hdc", [...targetArgs, "shell", "ls", "/data/log/app.log"]);
if (ls.code !== 0) throw new Error(`device file missing: /data/log/app.log`);
await ex.pullFile("/data/log/app.log", "/tmp/app.log");
Defensive patterns

Strategy: validation

Validate before calling

// confirm the device and file exist before pullFile
const targets = await run("hdc", ["list", "targets"]);
if (!targets.stdout.trim()) throw new Error("no hdc device connected");
const ls = await run("hdc", [...targetArgs, "shell", "ls", remotePath]);
if (ls.code !== 0) throw new Error(`device file missing: ${remotePath}`);

Type guard

function isHdcRecvFailure(e) { return e instanceof ExecError && e.message.startsWith("hdc file recv failed"); }

Try / catch

try {
  const local = await ex.pullFile(remotePath, localPath);
} catch (e) {
  if (e.message.startsWith("hdc file recv failed")) {
    console.error("hdc recv failed:", e.message); // embedded stderr: path vs connection
    // reconnect device or correct the device path, then retry
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling executor.pullFile(remotePath, localPath) when hdc exits nonzero: device not connected, remote path does not exist on device, permission denied on device path, or localPath not writable.

Common situations: HarmonyOS device disconnected or unauthorized; typo'd device path (file absent); target selector (targetArgs) pointing at the wrong connected device; local destination directory missing; hdc daemon not running on the device.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/1a6721c43dfc0279. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/transport.mjs:310

  return { installed: rels.length, remotePlatform: reply.platform, agentPath: `${marker}/agent.mjs` };
}

/** hdc (HarmonyOS) executor. Commands run on-device; files pull to local tmp. */
export function hdcExec(computer) {
  const targetArgs = computer.target ? ["-t", computer.target] : [];
  const shell = (args, opts = {}) => run("hdc", [...targetArgs, "shell", ...args], opts);
  return {
    kind: "hdc",
    targetArgs,
    run,
    runOk,
    shell,
    async pullFile(remotePath, localPath, opts = {}) {
      // HDC device captures use absolute paths; SSH agent paths are relative.
      // Validate the remaining path with the same traversal/metacharacter guard.
      safeRemotePath(typeof remotePath === "string" ? remotePath.replace(/^\//, "") : remotePath);
      const r = await run("hdc", [...targetArgs, "file", "recv", remotePath, localPath], opts);
      if (r.code !== 0) throw new ExecError(`hdc file recv failed: ${r.stderr.trim().slice(0, 300)}`, r);
      return localPath;
    },
    async readFile(remotePath, opts = {}) {
      // Containment: pull into a private mkdtemp dir and remove exactly that
      // dir. Never rm() the parent of a file placed directly in os.tmpdir() —
      // that recursively deletes the entire user temp directory.
      const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "cu-hdc-"));
      try {
        const tmp = path.join(dir, "out");
        await this.pullFile(remotePath, tmp, opts);
        return await fs.promises.readFile(tmp);
      } finally {
        // Cleanup must not replace downloaded bytes or the original I/O error.
        await fs.promises.rm(dir, { recursive: true, force: true }).catch(() => {});
      }
    },
  };
}

View on GitHub (pinned to 73e0f67d83)