santifer/career-ops · error · Error

local-parser: careers_url is not a valid URL

Error message

local-parser: careers_url is not a valid URL: ${value}

What it means

safeCareersUrl() validates the {careers_url} placeholder before it is interpolated into a local parser's argv. If the value is empty-ish but truthy-unparseable — i.e. String(value) cannot be parsed by `new URL()` — it throws this error rather than passing a malformed URL to a subprocess.

Solutions

  1. Correct the entry's careers_url in portals.yml to a fully qualified URL including the scheme, e.g. https://acme.com/careers.
  2. URL-encode special characters (spaces, non-ASCII) in the configured value.
  3. If the URL is optional for this parser, remove `{careers_url}` from the parser args so the validator is never invoked.
  4. Note: an empty/falsy careers_url returns '' silently — this error only fires for non-empty unparseable values, so check for whitespace-only strings too (they fail new URL()).

Example fix

// before (portals.yml)
careers_url: acme.com/jobs
args: ["parser.py", "{careers_url}"]
// after
careers_url: https://acme.com/jobs
args: ["parser.py", "{careers_url}"]
Defensive patterns

Strategy: validation

Validate before calling

let u;
try { u = new URL(entry.careers_url); } catch { throw new Error(`entry ${entry.name}: careers_url must be absolute, e.g. https://...`); }

Try / catch

try {
  await localParser.fetch(entry);
} catch (e) {
  if (String(e.message).includes('careers_url is not a valid URL')) {
    console.error(`Config error in ${entry.name}: fix careers_url to an absolute http(s) URL`);
    return [];
  }
  throw e;
}

Prevention

When it happens

Trigger: A portals.yml entry with parser args containing `{careers_url}` whose entry.careers_url is a malformed string (missing scheme, spaces, garbage) — `new URL(value)` throws and this error propagates out of expandParserArg during resolveInvocation/buildParserArgs.

Common situations: Careers URL entered without scheme ('acme.com/careers'); URL with unescaped spaces or stray characters; a copy-paste that included surrounding markdown or quotes; an entry where careers_url holds a relative path instead of an absolute URL.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/3216a56453cd4270. Report an issue: GitHub.

Appendix: source

Thrown at providers/local-parser.mjs:29

const LOCAL_PARSER_TIMEOUT_MS = 20_000;
const LOCAL_PARSER_MAX_BUFFER_BYTES = 2_000_000;

// `parser.command` / `parser.script` come from portals.yml, which on a shared or
// template config is not fully trusted. The command must be a known interpreter
// or a file inside this project — never an arbitrary binary like `rm` or `curl`.
const PROJECT_ROOT = realpathSync(resolve(fileURLToPath(new URL('..', import.meta.url))));
const ALLOWED_INTERPRETERS = new Set(['python3', 'python', 'node', 'deno', 'bun', 'sh', 'bash']);

// `{careers_url}` and `{company}` are interpolated into the parser's argv. Validate
// them so an interpolated value can never be read as a CLI flag (argument injection).
function safeCareersUrl(value) {
  if (!value) return '';
  let url;
  try {
    url = new URL(String(value));
  } catch {
    throw new Error(`local-parser: careers_url is not a valid URL: ${value}`);
  }
  if (url.protocol !== 'http:' && url.protocol !== 'https:') {
    throw new Error(`local-parser: careers_url must be http(s): ${value}`);
  }
  return url.href;
}

function safeCompany(value) {
  if (!value) return '';
  const name = String(value).trim();
  // execFile passes args verbatim (no shell), so the only injection risk is a
  // value that begins like a CLI flag.
  if (name.startsWith('-')) {
    throw new Error(`local-parser: company name cannot start with '-': ${value}`);
  }
  return name;
}

View on GitHub (pinned to aac998c7ed)