Hmbown/CodeWhale · error · ExecError
docker exec : agent returned no JSON receipt
Error message
docker exec ${container}: agent returned no JSON receipt What it means
After a successful (exit 0) `docker exec` run, the transport looks for the last stdout line starting with '{' as the agent's JSON reply and throws this ExecError if none exists. The agent must print exactly one JSON receipt line; this error means it printed none.
Solutions
- docker exec <container> /bin/sh <remoteAgent> with sample args to inspect raw output
- Reinstall the agent into the container (re-run the docker agent install path)
- Verify the agent file inside the container is non-empty and is the real agent.mjs
- Check node availability/version in the container
- Confirm nothing in the container's shell setup discards stdout
Example fix
// before
await ex.remote({ tool: "platform" }); // no JSON receipt
// after
const chk = await run("docker", ["exec", container, "/bin/sh", "-c", `wc -c ${remoteAgent}`]);
if (chk.code !== 0 || chk.stdout.trim().startsWith("0")) await installDockerAgent(ex, computer);
await ex.remote({ tool: "platform" }); Defensive patterns
Strategy: try-catch
Validate before calling
const chk = await run("docker", ["exec", container, "/bin/sh", "-c", `test -s ${remoteAgent} && echo ok`]);
if (chk.code !== 0 || !chk.stdout.includes("ok")) await installDockerAgent(computer); Type guard
function isNoReceiptError(e) { return e instanceof ExecError && e.message.includes("no JSON receipt"); } Try / catch
try {
return await ex.remote(request, opts);
} catch (e) {
if (e.message.includes("no JSON receipt")) {
await installDockerAgent(computer); // reinstall, retry once
return ex.remote(request, opts);
}
throw e;
} Prevention
- Re-copy agent files into the container after every agent update
- Confirm the container's node can parse agent.mjs (syntax/version)
- Check that entrypoint/shell wrappers do not swallow the agent's stdout
When it happens
Trigger: executor.remote() on docker transport where the exec succeeds but stdout contains no '{'-prefixed line: agent missing/empty in the container, agent crashed after printing nothing, wrong file at the agent path, or stdout hijacked by wrapper scripts.
Common situations: Agent volume/upload missing in the container; entrypoint or shell rc echoes text but agent silently fails; stale container built without the agent; agent syntax unsupported by the container's node version.
Related errors
- docker exec exited
- docker exec : timed out
- ssh : agent returned no JSON receipt
- agy OAuth token JSON carries no access token member
- Antigravity cloud-code SSE is not JSON
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/77ea2a4a4279ff59.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/transport.mjs:262
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)) {
if (f.endsWith(".mjs") || f.endsWith(".m") || f.endsWith(".h")) rels.push(`src/${dir ? dir + "/" : ""}${f}`);
}
}View on GitHub (pinned to 73e0f67d83)