NousResearch/hermes-agent · error

Remote gateway URL is not valid: ${error.message}

Error message

Remote gateway URL is not valid: ${error.message}

What it means

ValueError from _validate_execution_mode (cron/jobs.py:1552), the single shared validator called by BOTH create_job and update_job: a job was given both monitor_script and monitor_url. A monitor job watches exactly one source for changes (to suppress or wake the agent), so two sources are ambiguous and rejected up front — the update door cannot reintroduce the violation either.

Source

Thrown at apps/desktop/electron/connection-config.ts:74

    throw new Error('Remote gateway URL is required.')
  }

  // Users routinely paste scheme-less "host:port" (a Tailscale IP, a LAN
  // hostname). Without this, `new URL('100.64.0.1:9119')` either throws or —
  // worse — parses `host:` as the protocol and produces a baffling
  // "must be http:// or https://, got myhost:" error. Only a real
  // `scheme://` prefix opts out, so explicit non-http schemes (ftp://,
  // file://) still reach the protocol check below and get rejected.
  if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) {
    value = `http://${value}`
  }

  let parsed

  try {
    parsed = new URL(value)
  } catch (error) {
    throw new Error(`Remote gateway URL is not valid: ${error.message}`)
  }

  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
    throw new Error(`Remote gateway URL must be http:// or https://, got ${parsed.protocol}`)
  }

  parsed.hash = ''
  parsed.search = ''
  parsed.pathname = parsed.pathname.replace(/\/+$/, '')

  return parsed.toString().replace(/\/+$/, '')
}

function buildGatewayWsUrl(baseUrl, token) {
  const parsed = new URL(baseUrl)
  const wsScheme = parsed.protocol === 'https:' ? 'wss' : 'ws'
  const prefix = parsed.pathname.replace(/\/+$/, '')

View on GitHub (pinned to c896c09c42)

Solutions

  1. Keep exactly one monitor source: pass only monitor_script or only monitor_url.
  2. To switch sources, first update the job setting the old field to None, then set the new one.
  3. If you truly need two sources, create two separate monitor jobs.

Example fix

# before
create_job(prompt=..., schedule="5m", monitor_script="curl -s x", monitor_url="https://x")

# after
create_job(prompt=..., schedule="5m", monitor_url="https://x")  # script removed
Defensive patterns

Strategy: validation

Validate before calling

def valid_monitor_args(monitor_script, monitor_url) -> bool:
    return not (bool(monitor_script) and bool(monitor_url))  # at most one source

Try / catch

try:
    create_job(..., monitor_script=ms, monitor_url=mu)
except ValueError as e:
    if "mutually exclusive" in str(e):
        create_job(..., monitor_url=mu)  # keep exactly one source, drop the other
        raise

Prevention

When it happens

Trigger: create_job(monitor_script='...', monitor_url='...') or update_job(id, {monitor_script: '...'}) on a job that already has monitor_url set (or vice versa).

Common situations: Iteratively building a monitor job and adding a URL after starting with a script; merging two job configs programmatically; an LLM 'upgrading' a script monitor to a URL monitor without clearing the old field.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/33725f6661c5dd8c. Report an issue: GitHub.