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
- 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.
- Check the entry's provider id is phenom and the field is not misspelled or misplaced in the YAML.
- Inspect providers/phenom.mjs resolveConfig to see exactly which keys it accepts and supply one.
- 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
- Add the origin/careers_url key exactly as resolveConfig in providers/phenom.mjs expects it.
- Schema-validate portals.yml entries per provider before running scans.
- Confirm the company is really on Phenom (audit-portals.mjs) before assigning provider: phenom.
- Check YAML indentation so the field belongs to the intended entry.
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
- oraclecloud: cannot derive API URL for
- personio: cannot derive feed URL for
- collage: cannot derive API URL for
- jibeapply: cannot derive API URL for
- jobbankca: entry " " has no jobbankca.keywords[] and no…
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 wasView on GitHub (pinned to aac998c7ed)