santifer/career-ops · error

gem: invalid URL

Error message

gem: invalid URL: ${url}

What it means

assertGemUrl validates a URL before Gem provider requests. When new URL(url) cannot parse the value (throwing SyntaxError internally), the library wraps it in this domain-specific error. It exists so callers get a clear provider-prefixed message instead of a raw URL parser exception.

Solutions

  1. Log the exact url value at the throw site and fix the unparseable portion (missing scheme, spaces, stray quotes).
  2. Trim whitespace and strip surrounding quotes from the URL before passing it.
  3. Ensure the config entry's URL field is populated — check portals.yml / the board entry for an empty or partially filled value.
  4. Pre-validate with new URL(url) in a try/catch at load time to fail fast with context.

Example fix

// before
assertGemUrl(process.env.GEM_BOARD_URL || '');
// after
const raw = (process.env.GEM_BOARD_URL || '').trim().replace(/^['"]|['"]$/g, '');
if (!raw) throw new Error('GEM_BOARD_URL is not set');
assertGemUrl(raw);
Defensive patterns

Strategy: validation

Validate before calling

function isParseableUrl(u) { try { new URL(u); return true; } catch { return false; } }
if (!isParseableUrl(cfg.url)) throw new Error(`bad Gem URL in config: ${JSON.stringify(cfg.url)}`);

Type guard

const isNonEmptyString = (v) => typeof v === 'string' && v.trim().length > 0;

Try / catch

try { assertGemUrl(url); } catch (e) { if (String(e.message).includes('invalid URL')) console.error('Check the URL for missing scheme/spaces:', url); throw e; }

Prevention

When it happens

Trigger: Passing a URL string that is not parseable by the WHATWG URL parser to assertGemUrl or any code path that resolves a Gem board entry's api/board URL — e.g. an empty string, a value with spaces like 'https://example.com/my board', a malformed scheme ('htp://...'), or a config field that was never populated and interpolated as ''.

Common situations: Typo in the careers_url or api field in portals.yml; an environment-variable-substituted URL that resolved to empty; copy-pasting a URL with surrounding whitespace/quotes; hand-editing board entries and dropping the scheme.

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/d92c596ba9b88dae. Report an issue: GitHub.

Appendix: source

Thrown at providers/gem.mjs:123

// to before this field list was widened.
/** @param {any} posting */
function buildJobDescriptionText(posting) {
  const intro = htmlToText(posting?.jobPostSectionHtml?.introHtml);
  const body = htmlToText(posting?.descriptionHtml);
  const outro = htmlToText(posting?.jobPostSectionHtml?.outroHtml);
  const compensation = htmlToText(posting?.compensationHtml);

  const text = [intro, body, outro].filter(Boolean).join('\n\n');
  return compensation ? [text, `Compensation: ${compensation}`].filter(Boolean).join('\n\n') : text;
}

/** @param {string} url */
function assertGemUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`gem: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`gem: URL must use HTTPS: ${url}`);
  if (!ALLOWED_GEM_HOSTS.has(parsed.hostname))
    throw new Error(`gem: untrusted hostname "${parsed.hostname}" — must be one of: ${[...ALLOWED_GEM_HOSTS].join(', ')}`);
  return url;
}

/** Resolve an explicitly pinned URL for Gem's documented REST job-board API. */
function resolveRestApiUrl(entry) {
  const raw = typeof entry.api === 'string' ? entry.api : '';
  if (!raw) return null;
  let parsed;
  try {
    parsed = new URL(raw);
  } catch {
    return null;
  }
  if (parsed.protocol !== 'https:' || parsed.hostname !== 'api.gem.com') return null;

View on GitHub (pinned to aac998c7ed)