santifer/career-ops · error · Error

phenom: cannot resolve origin for ${entry.name}

Error message

phenom: cannot resolve origin for ${entry.name}

What it means

Thrown by phenom's fetch() when resolveConfig(entry) returns null — the entry has no api or careers_url that can be parsed into a valid origin. resolveConfig() reads entry.api || entry.careers_url, parses it with new URL(), accepts http: or https: protocol, then builds a config object with the origin, widgetsApi path, and optional phenom block (urlPrefix, lang, country, selectedFields). If the URL is missing or unparseable, it returns null and fetch() cannot proceed. Unlike most providers, phenom's resolveConfig accepts http: as well as https:.

Source

Thrown at providers/phenom.mjs:158

    });
  }
  return { total, rows };
}

/** Resolve the page cap: a positive integer `max_pages` on the entry, capped. */
export function resolveMaxPages(entry) {
  const v = entry?.max_pages;
  if (Number.isInteger(v) && v > 0) return Math.min(v, MAX_PAGES_CAP);
  return DEFAULT_MAX_PAGES;
}

/** @type {Provider} */
export default {
  id: 'phenom',

  async fetch(entry, ctx) {
    const cfg = resolveConfig(entry);
    if (!cfg) throw new Error(`phenom: cannot resolve origin for ${entry.name}`);

    const wait = (ms) => (ctx.sleep ? ctx.sleep(ms) : new Promise((r) => setTimeout(r, ms)));
    const maxPages = resolveMaxPages(entry);
    // Honor a context page cap — verify-portals' liveness probe sets
    // `ctx.maxPages: 1` so it only needs to know a board is live, not its
    // full count (mirrors providers/workday.mjs). Kept separate from
    // maxPages so the entry-cap warning below (page === maxPages) doesn't
    // misfire when it was really the probe's cap that stopped pagination.
    // No effect on real scans, which don't set ctx.maxPages.
    const ctxCap = Number.isInteger(ctx?.maxPages) && ctx.maxPages > 0 ? ctx.maxPages : Infinity;
    const pagesToFetch = Math.min(maxPages, ctxCap);
    const jobs = [];
    const seen = new Set();
    let total = null;

    // Why pagination stopped — drives the truncation warning below. Only a
    // 'cap' stop is worth surfacing: 'complete' means the widget ran out of
    // fresh rows on its own, and a fetch failure already keeps whatever was

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Add a valid api or careers_url to the phenom entry in portals.yml (e.g. https://careers.company.com).
  2. Verify entry.api || entry.careers_url evaluates to a non-empty string before calling fetch().
  3. Pre-validate entries with a detect()-like check (resolveConfig returning non-null) before dispatching to fetch().
  4. Check for config-merge or migration issues that removed the URL field.

Example fix

// before — phenom entry with no URL
job_boards:
  acme:
    provider: phenom
    name: Acme
    # no api or careers_url

// after
job_boards:
  acme:
    provider: phenom
    name: Acme
    careers_url: 'https://careers.acme.com'
    phenom:
      urlPrefix: 'global/en'
      lang: 'en_global'
      country: 'global'
Defensive patterns

Strategy: validation

Validate before calling

/** Replicate resolveConfig to validate an entry before fetch. */
function canResolvePhenomConfig(entry) {
  const raw = entry.api || entry.careers_url || '';
  if (!raw) return false;
  try {
    const u = new URL(raw);
    return u.protocol === 'https:' || u.protocol === 'http:';
  } catch {
    return false;
  }
}

if (!canResolvePhenomConfig(entry)) {
  console.warn(`phenom entry ${entry.name} cannot resolve config — skipping`);
  continue;
}

Type guard

/** @param {import('./_types.js').PortalEntry} entry @returns {boolean} */
function hasPhenomUrl(entry) {
  const url = entry.api || entry.careers_url;
  return typeof url === 'string'
    && url.length > 0
    && (() => { try { new URL(url); return true; } catch { return false; } })();
}

Try / catch

try {
  await phenomProvider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).startsWith('phenom: cannot resolve origin')) {
    console.warn(`skipping ${entry.name}: no valid Phenom career URL`);
    continue;
  }
  throw err;
}

Prevention

When it happens

Trigger: resolveConfig returns null when: (1) both entry.api and entry.careers_url are empty/missing/falsy; (2) new URL() throws on the value. Once past parsing, resolveConfig is permissive (accepts http:, any hostname), so hostname/SRFF concerns are lower for phenom than for other providers. detect() is not shown calling resolveConfig, so this throw typically fires when fetch() is called with an entry that wasn't pre-validated.

Common situations: Portals.yml phenom entry missing api and careers_url. A config merge dropped the URL field. A batch script generated entries without the URL. The entry was constructed for a different provider and dispatched to phenom by mistake.

Related errors


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