{"record":{"id":"576df39faf868ccf","repo":"NousResearch/hermes-agent","slug":"remote-path-must-not-be-empty","errorCode":null,"errorMessage":"Remote path must not be empty.","messagePattern":"Remote path must not be empty\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/remote-lifecycle.ts","lineNumber":101,"sourceCode":"\nfunction lockfilePath(ownershipId) {\n  return `${ownershipDirectory(ownershipId)}/backend.lock.json`\n}\n\nfunction spawnLogPath(ownershipId, spawnNonce) {\n  return `${ownershipDirectory(ownershipId)}/${validateSpawnNonce(spawnNonce)}.log`\n}\n\n// shell-single-quote a value for safe interpolation into a remote command.\nfunction shq(value) {\n  return `'${String(value).replace(/'/g, `'\\\\''`)}'`\n}\n\nfunction validateRemotePath(p) {\n  const s = String(p || '')\n\n  if (!s) {\n    throw new Error('Remote path must not be empty.')\n  }\n\n  // eslint-disable-next-line no-control-regex -- deliberately reject NUL in remote paths\n  if (/[\\x00\\n\\r]/.test(s)) {\n    throw new Error('Unsafe remote path: contains NUL or newline.')\n  }\n\n  if (s === '~' || s.startsWith('~/') || s.startsWith('/')) {\n    return\n  }\n\n  throw new Error(`Remote path must be absolute or start with ~/: \"${s}\"`)\n}\n\nfunction expandRemotePath(p) {\n  validateRemotePath(p)\n\n  if (p === '~') {","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/remote-lifecycle.ts#L83-L119","documentation":"Thrown by validateRemotePath() in the desktop app's remote-lifecycle module, which guards every remote path before it is interpolated into an SSH command via expandRemotePath(). An empty path is rejected outright because expanding it would produce a malformed or dangerous remote command.","triggerScenarios":"Calling expandRemotePath() (or any path it validates) with undefined, null, '', or a value whose String(p || '') is empty — e.g. a remote config field like a workdir or log directory that was never filled in, or a default of undefined passed straight through.","commonSituations":"A remote backend config where the workspace/log path field is optional but downstream code assumes it is set; code defaulting a path variable to undefined instead of a concrete '~/...' default; passing an empty string from trimmed user input.","solutions":["Supply a concrete default before expansion: expandRemotePath(cfg.workdir || '~/hermes').","Make the path required in the remote settings UI and validate non-empty before save.","If empty is legitimately allowed at a call site, branch around the call instead of passing '' through to expandRemotePath()."],"exampleFix":"// before\nconst dir = expandRemotePath(config.logDir)\n\n// after\nconst dir = expandRemotePath(config.logDir || '~/hermes/logs')","handlingStrategy":"validation","validationCode":"const pathInput = String(config.remotePath ?? '').trim()\nif (!pathInput) {\n  // apply a default or reject at the UI layer before expandRemotePath runs\n  config.remotePath = '~/hermes'\n}","typeGuard":null,"tryCatchPattern":"try {\n  return expandRemotePath(p)\n} catch (e) {\n  if (e instanceof Error && e.message === 'Remote path must not be empty.') {\n    return '\"$HOME\"' // or apply a sane default\n  }\n  throw e\n}","preventionTips":["Default optional remote paths to '~/...' at config load time.","Mark the path field required in the remote settings form.","Never pass possibly-undefined config fields directly to remote-lifecycle helpers."],"tags":["validation","remote","ssh","path"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}