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
- Test manually: ssh <userHost> 'mkdir -p .codewhale-cu/agent/src/backends' and read the full error
- Verify SSH connectivity/auth (keys, agent forwarding, known_hosts)
- Check remote home writability and disk quota
- Confirm the user's shell is interactive enough to run mkdir
- 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
- Set up ssh keys and confirm interactive ssh works before installing
- Check remote home directory is writable and has free quota
- Ensure the remote account's shell can execute simple commands
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)