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
- Correct the entry's careers_url in portals.yml to a fully qualified URL including the scheme, e.g. https://acme.com/careers.
- URL-encode special characters (spaces, non-ASCII) in the configured value.
- If the URL is optional for this parser, remove `{careers_url}` from the parser args so the validator is never invoked.
- 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
- Always store careers_url fully qualified with scheme in portals.yml.
- Run a one-off config lint that new URL()s every careers_url before scans.
- Avoid copy-pasting URLs with trailing punctuation or markdown.
- Keep placeholders ({careers_url}) only in args of parsers that actually need them.
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
- arbeitnow: invalid URL
- ashby: invalid URL
- bamboohr: invalid URL
- breezy: invalid URL
- builtin: invalid URL
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)