santifer/career-ops · error · Error

gem: invalid URL: ${url}

Error message

gem: invalid URL: ${url}

What it means

gem.mjs throws this inside assertGemUrl() when `new URL(url)` raises — i.e. the string is not an absolute, parseable URL. The guard runs before every POST to the Gem GraphQL batch endpoint. In production the validated value is the module constant GEM_API_URL ('https://jobs.gem.com/api/public/graphql/batch'), so a throw here means that constant was corrupted/injected or a test called assertGemUrl() with a relative/malformed string.

Source

Thrown at providers/gem.mjs:111

// 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;
}

/** @param {import('./_types.js').PortalEntry} entry */
function resolveBoardId(entry) {
  const raw = typeof entry.careers_url === 'string' ? entry.careers_url : '';
  if (!raw) return null;
  let parsed;
  try {
    parsed = new URL(raw);
  } catch {
    return null;
  }
  if (parsed.hostname !== 'jobs.gem.com') return null;

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Restore GEM_API_URL to the full absolute form 'https://jobs.gem.com/api/public/graphql/batch'.
  2. If you meant to test the guard, pass an absolute-but-invalid string (e.g. 'https://') only when you intend to assert the throw.
  3. Audit any code that builds/mutates GEM_API_URL at runtime — it is meant to be a literal constant.

Example fix

// before
const GEM_API_URL = '/api/public/graphql/batch'; // relative -> new URL() throws

// after
const GEM_API_URL = 'https://jobs.gem.com/api/public/graphql/batch';
Defensive patterns

Strategy: validation

Validate before calling

// Startup self-check: GEM_API_URL must be an absolute URL.
function checkGemApiUrl() {
  try { new URL('https://jobs.gem.com/api/public/graphql/batch'); }
  catch { throw new Error('GEM_API_URL is not a valid absolute URL'); }
}

Try / catch

// Defensive: validate before the first request so the error surfaces with context.
try { assertGemUrl(GEM_API_URL); }
catch (err) { throw new Error(`gem endpoint misconfigured: ${err.message}`); }

Prevention

When it happens

Trigger: GEM_API_URL was edited to a relative path or a string with illegal characters (spaces, stray quotes); an env var or config merge injected an empty/undefined-coerced string; a test invokes assertGemUrl('/api/graphql/batch') or assertGemUrl(undefined).

Common situations: A contributor changes the endpoint to a path-only string ('/api/public/graphql/batch') thinking it will be resolved against a base; a templating/CI step mangled the constant; a unit test passes a deliberately bad URL but the test expected a different message.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/d92c596ba9b88dae. Report an issue: GitHub.