{"record":{"id":"606be53e8d74cb9b","repo":"NousResearch/hermes-agent","slug":"remote-gateway-url-is-required","errorCode":null,"errorMessage":"Remote gateway URL is required.","messagePattern":"Remote gateway URL is required\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/connection-config.ts","lineNumber":56,"sourceCode":"const RT_COOKIE_VARIANTS = ['__Host-hermes_session_rt', '__Secure-hermes_session_rt', 'hermes_session_rt']\n\n// The Nous portal (NAS) does NOT use Hermes gateway session cookies — it is a\n// Privy-authed Next.js app. NAS `auth()` (src/server/auth/session.ts) reads the\n// `privy-token` access-token cookie (with `privy-id-token` alongside), which is\n// also exactly what the `/api/agents` cookie-auth path validates. So portal\n// sign-in / discovery liveness must look for the Privy cookie, NOT the gateway\n// cookies above. `privy-token` is the access token (the required signal);\n// variants cover the secured-prefix forms and the older `privy-session` name.\nconst PRIVY_SESSION_COOKIE_VARIANTS = ['__Host-privy-token', '__Secure-privy-token', 'privy-token', 'privy-session']\n// Keep this aligned with hermes_cli.profiles.validate_profile_name(). `default`\n// is the built-in root alias; these names cannot be created as profiles.\nconst RESERVED_REMOTE_PROFILES = new Set(['hermes', 'test', 'tmp', 'root', 'sudo'])\n\nfunction normalizeRemoteBaseUrl(rawUrl) {\n  let value = String(rawUrl || '').trim()\n\n  if (!value) {\n    throw new Error('Remote gateway URL is required.')\n  }\n\n  // Users routinely paste scheme-less \"host:port\" (a Tailscale IP, a LAN\n  // hostname). Without this, `new URL('100.64.0.1:9119')` either throws or —\n  // worse — parses `host:` as the protocol and produces a baffling\n  // \"must be http:// or https://, got myhost:\" error. Only a real\n  // `scheme://` prefix opts out, so explicit non-http schemes (ftp://,\n  // file://) still reach the protocol check below and get rejected.\n  if (!/^[a-z][a-z0-9+.-]*:\\/\\//i.test(value)) {\n    value = `http://${value}`\n  }\n\n  let parsed\n\n  try {\n    parsed = new URL(value)\n  } catch (error) {\n    throw new Error(`Remote gateway URL is not valid: ${error.message}`)","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/connection-config.ts#L38-L74","documentation":"ValueError from _validate_workdir (cron/jobs.py:1427): the workdir exists but is not a directory (it's a regular file, symlink-to-file, device, etc.). Cron jobs use workdir as the execution directory for AGENTS.md loading and terminal cwd, so a file target is invalid. Checked after existence so the message is precise.","triggerScenarios":"create_job/update_job with workdir pointing at a file — e.g. '/home/me/proj/run.sh', or a directory replaced by a file of the same name, or a broken setup where a mount point got overmounted by a file.","commonSituations":"User pastes a script path instead of its containing directory; a project replaced by a single archive file; path copied from docs with a filename appended by mistake.","solutions":["Point workdir at the containing directory, not a file: '/home/me/proj' instead of '/home/me/proj/run.sh'.","If the target should be a directory, remove the file and mkdir the directory.","Double-check trailing components of long copy-pasted paths."],"exampleFix":"# before\ncreate_job(..., workdir=\"/home/me/proj/main.py\")\n\n# after\ncreate_job(..., workdir=\"/home/me/proj\")","handlingStrategy":"type-guard","validationCode":"from pathlib import Path\n\ndef valid_workdir(w: str) -> bool:\n    p = Path(w).expanduser()\n    return p.is_absolute() and p.is_dir()  # existence + is-a-directory in one check","typeGuard":"from pathlib import Path\n\ndef is_workdir(p: Path) -> bool:\n    \"\"\"Narrows to an absolute, existing directory Path accepted by cron workdir validation.\"\"\"\n    return p.is_absolute() and p.exists() and p.is_dir()","tryCatchPattern":"try:\n    create_job(..., workdir=w)\nexcept ValueError as e:\n    if \"is not a directory\" in str(e):\n        # caller passed a file path — use its parent, or fix the target\n        raise","preventionTips":["Pass directories, never file paths; if you copied the path from a command line, strip the filename.","Use Path.is_dir() as the pre-check — it covers existence and type together."],"tags":["cron","workdir","paths","validation"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}