santifer/career-ops · error

gem: URL must use HTTPS

Error message

gem: URL must use HTTPS: ${url}

What it means

URL scheme guard in the GEM board provider (assertGemUrl): the parsed careers/board URL's protocol is not https:. The URL parsed successfully but uses http: or another scheme; the provider refuses to fetch over a non-HTTPS connection. The input at fault is the careers_url configured for the board.

Solutions

  1. Change the URL scheme from http:// to https:// in the config entry.
  2. Verify the host actually serves HTTPS (test with curl -I https://...); most Gem boards do.
  3. If this is a local/dev stub, use an HTTPS local endpoint or a test double instead of downgrading the scheme.

Example fix

// before
assertGemUrl('http://boards.gem.com/company');
// after
assertGemUrl('https://boards.gem.com/company');
Defensive patterns

Strategy: validation

Validate before calling

if (!u.startsWith('https://')) throw new Error(`Gem URLs must be https://, got: ${u}`);

Type guard

const isHttpsUrl = (u) => { try { return new URL(u).protocol === 'https:'; } catch { return false; } };

Try / catch

try { assertGemUrl(url); } catch (e) { if (e.message.includes('must use HTTPS')) throw new Error(`Fix scheme for ${url}: use https://`); throw e; }

Prevention

When it happens

Trigger: Passing 'http://boards.gem.com/...' or any ftp:/ws:/file: scheme URL into assertGemUrl, or configuring a board entry whose api/careers URL starts with http:// in portals.yml.

Common situations: Copying an HTTP link from an older config or docs snippet; a redirect target recorded as http://; internal staging boards configured with http for local testing and forgotten.

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@aac998c7ed (2026-09-16). Data as JSON: /api/errors/5f6c85165ae48efe. Report an issue: GitHub.

Appendix: source

Thrown at providers/gem.mjs:125

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;
  if (!/^\/job_board\/v0\/[^/?#]+\/job_posts\/?$/.test(parsed.pathname)) return null;
  return parsed;

View on GitHub (pinned to aac998c7ed)