{"record":{"id":"af6b5bc262d3f70d","repo":"NousResearch/hermes-agent","slug":"sshconnection-requires-a-host","errorCode":null,"errorMessage":"SshConnection requires a host.","messagePattern":"SshConnection requires a host\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/ssh-connection.ts","lineNumber":475,"sourceCode":"\nclass SshConnection {\n  host: string\n  user: string\n  port: number\n  keyPath: string\n  controlPath: string\n  _spawnFn: any\n  _log: (msg: string) => void\n  _connectTimeoutMs: number\n  _execTimeoutMs: number\n  _forwardTimeoutMs: number\n  _opened: boolean\n  _mux: boolean\n  _tunnels: Map<string, any>\n\n  constructor(cfg, opts: any = {}) {\n    if (!cfg || !cfg.host) {\n      throw new Error('SshConnection requires a host.')\n    }\n\n    const port = cfg.port ? Number(cfg.port) : 22\n    validateSshTarget(cfg.host, cfg.user || '', port)\n\n    if (cfg.keyPath) {\n      validateKeyPath(cfg.keyPath)\n    }\n\n    this.host = cfg.host\n    this.user = cfg.user || ''\n    this.port = port\n    this.keyPath = cfg.keyPath || ''\n    // Windows OpenSSH has no ControlMaster (mux sockets were never implemented\n    // on Win32) — fall back to one ssh invocation per operation and a\n    // persistent `ssh -N -L` child per tunnel. Empty controlPath routes the\n    // pure builders onto their no-mux form.\n    this._mux = opts.mux ?? process.platform !== 'win32'","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/ssh-connection.ts#L457-L493","documentation":"Constructor guard on SshConnection: the config object must exist and have a truthy host before any other validation or connection setup runs. It is the earliest possible failure for a malformed connection config — it fires before validateSshTarget, validateKeyPath, or control-path setup.","triggerScenarios":"new SshConnection(null), new SshConnection({}), or a config whose host field is missing/empty — e.g. deserializing a remote-backend entry that was never fully populated.","commonSituations":"Loading a saved remote backend whose JSON is missing the host key; passing a partially-built config object; a default-config factory returning {} before the user has filled anything in.","solutions":["Ensure the config passed to SshConnection is a fully populated object with a non-empty host string.","Check the persistence layer: the field written on save must be the field read on load (host).","Guard call sites: skip connection attempts when the stored backend lacks a host and route the user back to remote setup."],"exampleFix":"// before\nconst conn = new SshConnection(savedRemote)\n\n// after\nif (!savedRemote?.host) {\n  throw new TypeError('remote backend config is missing host; re-run remote setup')\n}\nconst conn = new SshConnection(savedRemote)","handlingStrategy":"type-guard","validationCode":"if (!cfg || typeof cfg !== 'object' || typeof cfg.host !== 'string' || !cfg.host) {\n  throw new TypeError('SshConnection config must be an object with a non-empty host string')\n}","typeGuard":"function isSshConnectionConfig(cfg: unknown): cfg is { host: string; user?: string; port?: number | string; keyPath?: string } {\n  return Boolean(cfg && typeof cfg === 'object' && typeof (cfg as any).host === 'string' && (cfg as any).host.length > 0)\n}","tryCatchPattern":"try {\n  conn = new SshConnection(cfg)\n} catch (e) {\n  if (e instanceof Error && e.message === 'SshConnection requires a host.') {\n    // config incomplete: route the user back to remote setup instead of crashing\n    openRemoteSetupDialog()\n    return null\n  }\n  throw e\n}","preventionTips":["Validate deserialized remote configs before constructing connections.","Fail fast on partial saves: mark config records complete only after all required fields are written.","Centralize config shape checks in one loader so every call site is guarded."],"tags":["ssh","validation","constructor"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}