Hmbown/CodeWhale · error · ExecError

docker exec exited

Error message

docker exec ${container} exited ${r.code}: ${r.stderr.trim().slice(0, 400)}

What it means

The Docker transport throws this ExecError when `docker exec` exits with a nonzero status; the first 400 characters of stderr are embedded for diagnosis. It means the docker invocation itself failed — container, path, or agent problems — before any receipt could be returned.

Solutions

  1. Check container state: docker ps -a | grep <container> and restart it if stopped
  2. Verify the agent is installed inside the container at the expected path
  3. Confirm the image contains /bin/sh and node: docker exec <container> /bin/sh -c 'node --version'
  4. Read the stderr embedded in the error for the specific docker/agent failure
  5. Re-run the agent installation step for docker transports

Example fix

// before
await ex.remote({ tool: "platform" }); // container stopped
// after
await run("docker", ["start", container]);
await ex.remote({ tool: "platform" });
Defensive patterns

Strategy: try-catch

Validate before calling

const st = await run("docker", ["inspect", "-f", "{{.State.Running}}", container]);
if (st.code !== 0 || st.stdout.trim() !== "true") throw new Error(`container ${container} not running`);

Type guard

function isDockerExitError(e) { return e instanceof ExecError && /docker exec .* exited \d+:/.test(e.message); }

Try / catch

try {
  return await ex.remote(request, opts);
} catch (e) {
  if (/docker exec .* exited \d+:/.test(e.message)) {
    console.error("docker exec failed:", e.message); // embedded stderr names the cause
    // e.g. restart container or reinstall agent, then retry
  }
  throw e;
}

Prevention

When it happens

Trigger: executor.remote() on docker transport with r.code !== 0: container not running, /bin/sh or remoteAgent path missing inside the container, agent crashed with nonzero exit, docker daemon permission errors.

Common situations: Container stopped/exited since registration; agent not installed in the container; image lacking node or /bin/sh (distroless images); docker permission denied for the current user (not in docker group).

Related errors


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

Appendix: source

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

 */
export function dockerExec(computer, binding) {
  const container = safeRemotePath(computer.container);
  const remoteAgent = "/app/docker/agent-exec.sh";
  const ex = {
    kind: "docker",
    container,
    remoteAgent,
    run(cmd, args = [], opts = {}) {
      // Local side commands (docker itself) run directly.
      return run(cmd, args, opts);
    },
    async remote(request, opts = {}) {
      const r = await run("docker", ["exec", container, "/bin/sh", remoteAgent, b64({ args: request.args ?? {}, tool: request.tool, nonce: crypto.randomBytes(6).toString("hex") })], {
        timeoutMs: opts.timeoutMs ?? 25_000,
      });
      if (r.aborted) throw Object.assign(new ExecError("computer request cancelled", r), { code: "cancelled" });
      if (r.timedOut) throw new ExecError(`docker exec ${container}: timed out`, r);
      if (r.code !== 0) throw new ExecError(`docker exec ${container} exited ${r.code}: ${r.stderr.trim().slice(0, 400)}`, r);
      const line = r.stdout.trim().split("\n").filter((l) => l.startsWith("{")).pop();
      const reply = line ? JSON.parse(line) : null;
      if (!reply) throw new ExecError(`docker exec ${container}: agent returned no JSON receipt`, r);
      return reply;
    },
  };
  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)) {

View on GitHub (pinned to 73e0f67d83)