{"record":{"id":"eb587401883b085d","repo":"Hmbown/CodeWhale","slug":"ssh-userhost-agent-returned-no-json-receipt","errorCode":null,"errorMessage":"ssh ${userHost}: agent returned no JSON receipt","messagePattern":"ssh (.+?): agent returned no JSON receipt","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/transport.mjs","lineNumber":227,"sourceCode":"    kind: \"ssh\",\n    base,\n    userHost,\n    remoteAgent,\n    run(cmd, args = [], opts = {}) {\n      // Local side commands (e.g. ssh itself) run directly.\n      return run(cmd, args, opts);\n    },\n    async remote(request, opts = {}) {\n      const r = await run(\"ssh\", [...base, \"node\", remoteAgent, b64({ args: request.args ?? {}, tool: request.tool, nonce: crypto.randomBytes(6).toString(\"hex\") })], {\n        timeoutMs: opts.timeoutMs ?? 25_000,\n      });\n      if (r.aborted) throw Object.assign(new ExecError(\"computer request cancelled\", r), { code: \"cancelled\" });\n      if (r.timedOut) throw new ExecError(`ssh ${userHost}: timed out`, r);\n      if (r.code !== 0) throw new ExecError(`ssh ${userHost} exited ${r.code}: ${r.stderr.trim().slice(0, 400)}`, r);\n      // The agent prints exactly one JSON line; anything before it is MOTD noise.\n      const line = r.stdout.trim().split(\"\\n\").filter((l) => l.startsWith(\"{\")).pop();\n      const reply = line ? JSON.parse(line) : null;\n      if (!reply) throw new ExecError(`ssh ${userHost}: agent returned no JSON receipt`, r);\n      return reply;\n    },\n  };\n  attachPersistentChannel(ex, binding, [\"ssh\", ...base, \"node\", remoteAgent, \"--serve\"]);\n  return ex;\n}\n\n/**\n * docker executor: a spawned task-owned desktop container. Same agent contract\n * as ssh, but the channel is `docker exec` — no sshd, no keys, the container\n * boundary itself is the isolation. Every call goes through\n * docker/agent-exec.sh, which joins the desktop session env (display + bus)\n * the container entrypoint recorded before serving requests.\n */\nexport function dockerExec(computer, binding) {\n  const container = safeRemotePath(computer.container);\n  const remoteAgent = \"/app/docker/agent-exec.sh\";\n  const ex = {","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/transport.mjs#L209-L245","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait ex.remote({ tool: \"platform\" }); // fails: no JSON line\n// after\nconst r = await run(\"ssh\", [...base, \"cat\", \".codewhale-cu/agent/agent.mjs\"]); // verify agent exists\nif (r.code !== 0 || r.stdout.trim() === \"\") await installRemoteAgent(ex, computer);\nawait ex.remote({ tool: \"platform\" });","handlingStrategy":"try-catch","validationCode":"// verify agent file exists and is non-empty before remote calls\nconst chk = await run(\"ssh\", [...base, \"wc\", \"-c\", \".codewhale-cu/agent/agent.mjs\"]);\nif (chk.code !== 0 || parseInt(chk.stdout) === 0) await installRemoteAgent(ex, computer);","typeGuard":"function isNoReceiptError(e) { return e instanceof ExecError && e.message.includes(\"no JSON receipt\"); }","tryCatchPattern":"try {\n  const reply = await ex.remote(request, opts);\n} catch (e) {\n  if (e.message.includes(\"no JSON receipt\")) {\n    await installRemoteAgent(ex, computer); // reinstall then retry once\n    return ex.remote(request, opts);\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["ssh","json","remote-execution"],"backgroundTag":"unexpected-response-shape","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}