{"record":{"id":"877451b817ef3c8a","repo":"Hmbown/CodeWhale","slug":"ssh-userhost-timed-out","errorCode":null,"errorMessage":"ssh ${userHost}: timed out","messagePattern":"ssh (.+?): timed out","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/transport.mjs","lineNumber":222,"sourceCode":"  const userHost = computer.user ? `${computer.user}@${computer.host}` : computer.host;\n  const portArgs = computer.port ? [\"-p\", String(computer.port)] : [];\n  const remoteAgent = safeRemotePath(computer.agentPath ?? \".codewhale-cu/agent/agent.mjs\");\n  const base = [\"-o\", \"BatchMode=yes\", \"-o\", \"ConnectTimeout=8\", \"-o\", \"StrictHostKeyChecking=accept-new\", ...portArgs, userHost];\n  const ex = {\n    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.","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/transport.mjs#L204-L240","documentation":"Thrown by the remote transport's remote() when the ssh invocation to the user's host (running the remote node agent) exceeds its timeout (default 25s, overridable via opts.timeoutMs). The remote agent prints a JSON receipt over stdout; when ssh stalls instead of returning, the request is failed with this error rather than hanging.","triggerScenarios":"A remote computer request where `ssh [...userHost...] node <agent> ...` runs past timeoutMs — slow network, the remote host unreachable-but-not-refusing, or the in-container agent taking too long on a heavy tool call.","commonSituations":"Flaky VPN/Wi-Fi, sleeping remote machine waking up, DNS stalls, large tool operations on the remote desktop exceeding 25s, or ssh multiplexing/master connection degraded.","solutions":["Retry the request — transient ssh latency is the most common cause.","Pass a larger timeoutMs in opts for legitimately slow remote operations.","Check basic connectivity to userHost (`ssh <userHost> echo ok`) and fix network/VPN issues.","Set up ssh ControlMaster/keepalives to keep a warm connection and reduce per-call setup latency."],"exampleFix":"// before\nawait computer.remote({ tool: \"screenshot\", args: {} }); // times out at 25s\n// after\nawait computer.remote({ tool: \"screenshot\", args: {} }, { timeoutMs: 60_000 });","handlingStrategy":"retry","validationCode":"import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nconst pexec = promisify(execFile);\nawait pexec(\"ssh\", [userHost, \"echo\", \"ok\"], { timeout: 5_000 }); // host must answer","typeGuard":null,"tryCatchPattern":"try {\n  const reply = await computer.remote(req);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\": timed out\")) {\n    // retry with a larger timeoutMs; verify `ssh <userHost> echo ok` works\n  }\n}","preventionTips":["Configure ssh keepalives and ControlMaster for a warm connection.","Set a generous timeoutMs for heavy remote tool operations.","Keep the remote host awake (disable sleep or use wake-on-LAN).","Monitor VPN/network stability before batch remote operations."],"tags":["ssh","timeout","network","remote"],"backgroundTag":"request-timeout","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"}