santifer/career-ops · error · Error

pythonorg: invalid URL

Error message

pythonorg: invalid URL: ${url}

What it means

assertPythonOrgUrl validates any URL handed to the python.org jobs-feed provider. When the input cannot be parsed by the URL constructor at all (malformed syntax, missing scheme), it throws 'pythonorg: invalid URL: {url}'. It is the first of three checks, before the HTTPS and trusted-host checks, so configuration typos surface as a clear provider-namespaced error.

Solutions

  1. Fix the URL string so it is a valid absolute URL, e.g. 'https://www.python.org/jobs/feed/rss/'
  2. Add the missing scheme (https://) — the most common cause is a scheme-less host:port/path string
  3. Log/inspect the exact value passed in; unset or malformed env/config values are frequent culprits
  4. Wrap calls in a check like `new URL(url)` (or the defense validation code) before invoking the provider

Example fix

// before
assertPythonOrgUrl(config.feedUrl); // 'python.org/jobs/feed/rss/'
// after
const feedUrl = new URL(config.feedUrl).href; // throws with a clear syntax error, or fix config to 'https://www.python.org/jobs/feed/rss/'
assertPythonOrgUrl(feedUrl);
Defensive patterns

Strategy: validation

Validate before calling

export function isValidPythonOrgUrl(url) {
  try { new URL(url); return true; } catch { return false; }
}
// call before the provider: if (!isValidPythonOrgUrl(cfg.feedUrl)) throw new Error(`bad feedUrl: ${cfg.feedUrl}`)

Type guard

function isAbsoluteHttpUrl(value) {
  if (typeof value !== 'string') return false;
  try { const u = new URL(value); return u.protocol === 'http:' || u.protocol === 'https:'; } catch { return false; }
}

Try / catch

try {
  assertPythonOrgUrl(cfg.feedUrl);
} catch (e) {
  if (e.message.startsWith('pythonorg: invalid URL')) {
    console.error(`Config error: feedUrl '${cfg.feedUrl}' is not a parseable absolute URL — add the https:// scheme`);
    process.exitCode = 1;
  } else throw e;
}

Prevention

When it happens

Trigger: Calling assertPythonOrgUrl with a string the URL constructor rejects — e.g. 'python.org/jobs/feed/rss/' (no scheme), 'https://' (empty host), 'not a url', or a non-string coerced to something unparseable.

Common situations: A portals.yml job_boards entry with a typo'd or scheme-less URL; reading the URL from an env var that is unset or partially interpolated; concatenating base + path strings incorrectly; passing user-supplied input unvalidated.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@e7abd431fc (2026-09-22). Data as JSON: /api/errors/98011365bcb4c301. Report an issue: GitHub.

Appendix: source

Thrown at providers/pythonorg.mjs:26

// Python.org provider — the board-wide public RSS jobs feed at
// https://www.python.org/jobs/feed/rss/ (official Python Software Foundation job board).
// The feed is public, no-auth, and RSS 2.0 XML.
//
// Each <item> exposes <title> (typically "{Role}, {Company}"), <link>,
// and <description> (the first line typically contains the location).
//
// Wire in via a `job_boards:` entry with `provider: pythonorg`.

const FEED_URL = 'https://www.python.org/jobs/feed/rss/';
const TRUSTED_HOST = 'python.org';

/** @param {string} url */
export function assertPythonOrgUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`pythonorg: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`pythonorg: URL must use HTTPS: ${url}`);
  const host = parsed.hostname.toLowerCase();
  const trusted = host === TRUSTED_HOST || host.endsWith(`.${TRUSTED_HOST}`);
  if (!trusted) {
    throw new Error(`pythonorg: untrusted hostname "${parsed.hostname}" — must be ${TRUSTED_HOST}`);
  }
  return url;
}

// NaN-safe Date.parse — `|| undefined` would also coerce a valid epoch 0.
function toEpochMs(value) {
  if (!value) return undefined;
  const parsed = Date.parse(value);
  return Number.isNaN(parsed) ? undefined : parsed;
}

function fallbackCompany(entry) {

View on GitHub (pinned to e7abd431fc)