Hmbown/CodeWhale · error · ExecError
ssh : agent returned no JSON receipt
Error message
ssh ${userHost}: agent returned no JSON receipt What it means
After a successful (exit 0) ssh run, the transport filters stdout lines for ones starting with '{' and takes the last as the agent's JSON reply. If no such line exists (reply is null) it throws this ExecError: the agent produced no JSON receipt. The comment notes anything before the JSON line is MOTD noise, so this means the agent itself printed nothing parseable.
Solutions
- Re-run installRemoteAgent() to re-upload agent.mjs and backends
- Manually run ssh <userHost> 'node .codewhale-cu/agent/agent.mjs' to see raw stdout/stderr
- Check whether the remote shell prints banners that swallow the run; ensure agent writes its receipt to stdout
- Verify the remote file is not empty: ssh <userHost> 'wc -c .codewhale-cu/agent/agent.mjs'
- Confirm node version on remote supports the agent syntax
Example fix
// before
await ex.remote({ tool: "platform" }); // fails: no JSON line
// after
const r = await run("ssh", [...base, "cat", ".codewhale-cu/agent/agent.mjs"]); // verify agent exists
if (r.code !== 0 || r.stdout.trim() === "") await installRemoteAgent(ex, computer);
await ex.remote({ tool: "platform" }); Defensive patterns
Strategy: try-catch
Validate before calling
// verify agent file exists and is non-empty before remote calls
const chk = await run("ssh", [...base, "wc", "-c", ".codewhale-cu/agent/agent.mjs"]);
if (chk.code !== 0 || parseInt(chk.stdout) === 0) await installRemoteAgent(ex, computer); Type guard
function isNoReceiptError(e) { return e instanceof ExecError && e.message.includes("no JSON receipt"); } Try / catch
try {
const reply = await ex.remote(request, opts);
} catch (e) {
if (e.message.includes("no JSON receipt")) {
await installRemoteAgent(ex, computer); // reinstall then retry once
return ex.remote(request, opts);
}
throw e;
} Prevention
- Always run installRemoteAgent after agent code changes
- Beware hosts whose shell rc prints banners — confirm the agent's JSON reaches stdout
- Verify remote node version supports the agent's syntax
When it happens
Trigger: executor.remote() with r.code===0 but no stdout line starting with '{': remote agent crashed silently, printed only MOTD/banner text, wrong script executed (e.g. agent path points at a non-agent file), or the agent exited 0 without printing a receipt.
Common situations: Agent script not fully installed/truncated; remote shell profile emits banners and the agent failed silently; the wrong file uploaded to .codewhale-cu/agent/agent.mjs; node executed a stub or empty file; ssh to a host where the marker dir is empty.
Related errors
- docker exec : agent returned no JSON receipt
- ssh exited
- agy OAuth token JSON carries no access token member
- Antigravity cloud-code SSE is not JSON
- Burn rate is optional. When set, it must be a positive $/hr.
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/eb587401883b085d.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/transport.mjs:227
kind: "ssh",
base,
userHost,
remoteAgent,
run(cmd, args = [], opts = {}) {
// Local side commands (e.g. ssh itself) run directly.
return run(cmd, args, opts);
},
async remote(request, opts = {}) {
const r = await run("ssh", [...base, "node", 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(`ssh ${userHost}: timed out`, r);
if (r.code !== 0) throw new ExecError(`ssh ${userHost} exited ${r.code}: ${r.stderr.trim().slice(0, 400)}`, r);
// The agent prints exactly one JSON line; anything before it is MOTD noise.
const line = r.stdout.trim().split("\n").filter((l) => l.startsWith("{")).pop();
const reply = line ? JSON.parse(line) : null;
if (!reply) throw new ExecError(`ssh ${userHost}: agent returned no JSON receipt`, r);
return reply;
},
};
attachPersistentChannel(ex, binding, ["ssh", ...base, "node", remoteAgent, "--serve"]);
return ex;
}
/**
* docker executor: a spawned task-owned desktop container. Same agent contract
* as ssh, but the channel is `docker exec` — no sshd, no keys, the container
* boundary itself is the isolation. Every call goes through
* docker/agent-exec.sh, which joins the desktop session env (display + bus)
* the container entrypoint recorded before serving requests.
*/
export function dockerExec(computer, binding) {
const container = safeRemotePath(computer.container);
const remoteAgent = "/app/docker/agent-exec.sh";
const ex = {View on GitHub (pinned to 73e0f67d83)