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
- Check container state: docker ps -a | grep <container> and restart it if stopped
- Verify the agent is installed inside the container at the expected path
- Confirm the image contains /bin/sh and node: docker exec <container> /bin/sh -c 'node --version'
- Read the stderr embedded in the error for the specific docker/agent failure
- 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
- Ensure the container stays running between registrations
- Install the agent in the image or via the docker install path before use
- Use an image that includes /bin/sh and node (not distroless)
- Keep the current user in the docker group to avoid permission exits
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
- docker exec : agent returned no JSON receipt
- docker exec : timed out
- cleanup_failed
- clipboard read failed
- docker_unavailable
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)