santifer/career-ops · error · Error

phenom: cannot resolve origin for

Error message

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

What it means

The Phenom provider needs an explicit origin configuration (base URL of the Phenom-powered careers site) to paginate its API; resolveConfig(entry) returning null means the entry carries no usable origin. fetch() throws rather than guessing an origin, since Phenom boards are per-tenant and there is no global default. This is a configuration-resolution failure before any request is made.

Solutions

  1. Add the Phenom board origin (e.g. https://acme.wd3.myworkday... no — the Phenom host such as https://acme.phenompeople.com or the company's phenom careers host) to the entry per resolveConfig's expected key.
  2. Check the entry's provider id is phenom and the field is not misspelled or misplaced in the YAML.
  3. Inspect providers/phenom.mjs resolveConfig to see exactly which keys it accepts and supply one.
  4. Use audit-portals.mjs to confirm the careers_url belongs to a Phenom board; reassign providers otherwise.

Example fix

// before (portals.yml)
- name: Acme
  provider: phenom
// after
- name: Acme
  provider: phenom
  careers_url: https://acme.phenompeople.com
Defensive patterns

Strategy: validation

Validate before calling

function hasPhenomOrigin(entry) {
  const raw = typeof entry.careers_url === 'string' ? entry.careers_url : '';
  if (!raw) return false;
  try {
    const { protocol, hostname } = new URL(raw);
    return protocol === 'https:' && hostname.length > 0;
  } catch { return false; }
}
if (!hasPhenomOrigin(entry)) throw new Error(`phenom: entry ${entry.name} is missing a usable origin`);

Type guard

function hasPhenomCareersUrl(entry) {
  return entry != null && typeof entry === 'object' &&
    typeof entry.careers_url === 'string' && entry.careers_url.length > 0 &&
    (() => { try { return new URL(entry.careers_url).protocol === 'https:'; } catch { return false; } })();
}

Try / catch

try {
  await phenomProvider.fetch(entry, ctx);
} catch (e) {
  if (String(e.message).startsWith('phenom: cannot resolve origin')) {
    logger.warn({ entry: entry.name }, 'missing Phenom origin in config — skipping entry');
    return null;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling providers/phenom.mjs fetch(entry, ctx) when entry lacks the expected config keys (careers_url or the provider-specific origin field), the value is not a string, or it cannot be parsed into a valid origin.

Common situations: portals.yml entry for a Phenom company missing its origin/careers_url; wrong provider id (company actually on Workday/Greenhouse); YAML indentation making the field land under the wrong entry; Phenom tenant reached only via vanity domain not captured in config.

Understand the failure class

Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/dfa19fb30a737fa5. Report an issue: GitHub.

Appendix: source

Thrown at providers/phenom.mjs:169

    });
  }
  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 aac998c7ed)