{"record":{"id":"240ce3d3b6e152f0","repo":"NousResearch/hermes-agent","slug":"remote-gateway-url-must-be-http-or-https-go","errorCode":null,"errorMessage":"Remote gateway URL must be http:// or https://, got ${parsed.protocol}","messagePattern":"Remote gateway URL must be http:// or https://, got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/connection-config.ts","lineNumber":78,"sourceCode":"  // 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}`)\n  }\n\n  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n    throw new Error(`Remote gateway URL must be http:// or https://, got ${parsed.protocol}`)\n  }\n\n  parsed.hash = ''\n  parsed.search = ''\n  parsed.pathname = parsed.pathname.replace(/\\/+$/, '')\n\n  return parsed.toString().replace(/\\/+$/, '')\n}\n\nfunction buildGatewayWsUrl(baseUrl, token) {\n  const parsed = new URL(baseUrl)\n  const wsScheme = parsed.protocol === 'https:' ? 'wss' : 'ws'\n  const prefix = parsed.pathname.replace(/\\/+$/, '')\n\n  return `${wsScheme}://${parsed.host}${prefix}/api/ws?token=${encodeURIComponent(token)}`\n}\n\nfunction buildGatewayWsUrlWithTicket(baseUrl, ticket) {","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/connection-config.ts#L60-L96","documentation":"ValueError from _validate_execution_mode (cron/jobs.py:1557): a job combines monitor_script/monitor_url with no_agent=True. Monitor jobs exist to suppress or wake the agent based on source changes; no_agent=True removes the agent entirely, which would silently degrade the monitor into a plain script job. Enforced identically at create and update time.","triggerScenarios":"create_job(no_agent=True, monitor_url=...) or update_job(id, {'no_agent': True}) on an existing monitor job — flipping no_agent on later is exactly the 'update door' this shared validator closes.","commonSituations":"Trying to make a monitor job cheaper by removing the agent; retrofitting no_agent onto an existing monitor job to 'just run the check'; copying config between jobs of different kinds.","solutions":["If you don't need the agent, drop the monitor fields and use a plain no_agent script job.","If you need change detection, keep monitor_script/monitor_url and leave no_agent unset/False.","When updating, pass the full consistent mode — clear monitor fields AND set no_agent in one update if switching kinds."],"exampleFix":"# before\ncreate_job(prompt=..., schedule=\"10m\", no_agent=True, monitor_script=\"uptime\")\n\n# after\ncreate_job(prompt=..., schedule=\"10m\", no_agent=True, script=\"uptime | tee /tmp/u.log\")","handlingStrategy":"validation","validationCode":"def compatible_mode(monitor_script, monitor_url, no_agent) -> bool:\n    return not ((monitor_script or monitor_url) and no_agent)  # monitor implies agent","typeGuard":null,"tryCatchPattern":"try:\n    create_job(..., no_agent=True, monitor_url=u)\nexcept ValueError as e:\n    if \"cannot be combined with no_agent\" in str(e):\n        create_job(..., no_agent=True, script=s)  # plain script job instead\n        raise","preventionTips":["Decide the job kind up front: monitor job (agent + one source) OR no_agent script job — never both.","When flipping no_agent on update, clear monitor_script/monitor_url in the same updates dict."],"tags":["cron","monitor","no-agent","validation"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}