Hmbown/CodeWhale · error · ExecError

ssh : mkdir failed

Error message

ssh ${ex.userHost}: mkdir failed: ${r.stderr.trim().slice(0, 300)}

What it means

installRemoteAgent() first creates the remote directory tree .codewhale-cu/agent/src/backends via `ssh mkdir -p`. If that ssh command exits nonzero it throws this ExecError embedding trimmed stderr (max 300 chars). It means the remote directory could not be created, so no agent files can be uploaded.

Solutions

  1. Test manually: ssh <userHost> 'mkdir -p .codewhale-cu/agent/src/backends' and read the full error
  2. Verify SSH connectivity/auth (keys, agent forwarding, known_hosts)
  3. Check remote home writability and disk quota
  4. Confirm the user's shell is interactive enough to run mkdir
  5. Read the embedded stderr in the message for the precise cause

Example fix

// before
await installRemoteAgent(ex, computer); // auth fails -> mkdir failed
// after
const probe = await run("ssh", [...ex.base, "true"]);
if (probe.code !== 0) throw new Error(`ssh unreachable: ${probe.stderr}`); // diagnose first
await installRemoteAgent(ex, computer);
Defensive patterns

Strategy: try-catch

Validate before calling

const probe = await run("ssh", [...base, "mkdir", "-p", ".codewhale-cu/agent/src/backends"]);
if (probe.code !== 0) throw new Error(`cannot prepare remote dir: ${probe.stderr}`); // see full stderr before install

Type guard

function isMkdirFailure(e) { return e instanceof ExecError && e.message.includes("mkdir failed"); }

Try / catch

try {
  await installRemoteAgent(ex, computer);
} catch (e) {
  if (e.message.includes("mkdir failed")) {
    console.error("remote mkdir failed:", e.message); // auth/shell/quota issues
    // fix ssh access or remote permissions, then retry
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling installRemoteAgent() when the initial `ssh ... mkdir -p` fails: connection refused, auth failure, remote shell unavailable, home directory not writable, or disk full on the remote host.

Common situations: SSH keys not set up; wrong host/port/user in the binding; remote account with restricted shell (e.g. nologin, rbash); quota/disk-full on the remote home; SELinux or permissions preventing mkdir in home.

Related errors


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

Appendix: source

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

  };
  attachPersistentChannel(ex, binding, ["docker", "exec", "-i", container, "/bin/sh", remoteAgent, "--serve"]);
  return ex;
}

/** Push the self-contained remote agent + src tree to an ssh computer. */
export async function installRemoteAgent(computer) {
  const ex = sshExec(computer);
  const srcDir = path.join(PLUGIN_ROOT, "src");
  const rels = ["agent.mjs"];
  for (const dir of ["", "backends"]) {
    const full = path.join(srcDir, dir);
    for (const f of fs.readdirSync(full)) {
      if (f.endsWith(".mjs") || f.endsWith(".m") || f.endsWith(".h")) rels.push(`src/${dir ? dir + "/" : ""}${f}`);
    }
  }
  const marker = ".codewhale-cu/agent";
  let r = await run("ssh", [...ex.base, "mkdir", "-p", `${marker}/src/backends`], { timeoutMs: 15_000 });
  if (r.code !== 0) throw new ExecError(`ssh ${ex.userHost}: mkdir failed: ${r.stderr.trim().slice(0, 300)}`, r);
  for (const rel of rels) {
    const localPath = rel === "agent.mjs" ? path.join(PLUGIN_ROOT, "agent.mjs") : path.join(srcDir, rel.slice(4));
    const dest = safeRemotePath(`${marker}/${rel}`);
    r = await run("scp", [...(computer.port ? ["-P", String(computer.port)] : []), localPath, `${ex.userHost}:${dest}`], { timeoutMs: 30_000 });
    if (r.code !== 0) throw new ExecError(`scp ${rel} failed: ${r.stderr.trim().slice(0, 300)}`, r);
  }
  // Probe remote platform via the agent itself.
  const reply = await ex.remote({ tool: "platform" });
  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,

View on GitHub (pinned to 73e0f67d83)