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 wasView on GitHub (pinned to 9b17a8ac97)
Solutions
- Add a valid api or careers_url to the phenom entry in portals.yml (e.g. https://careers.company.com).
- Verify entry.api || entry.careers_url evaluates to a non-empty string before calling fetch().
- Pre-validate entries with a detect()-like check (resolveConfig returning non-null) before dispatching to fetch().
- 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
- Validate phenom entries have a non-empty api or careers_url before dispatching to fetch().
- Pre-check entries with resolveConfig (exported) at scan start to filter invalid ones.
- Ensure config merges preserve URL fields for phenom entries.
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
- nofluffjobs: careers_url or api must be a trusted nofluffjob
- oraclecloud: cannot derive API URL for ${entry.name}
- personio: cannot derive feed URL for ${entry.name}
- Unsupported profile photo style: ${style} (expected rounded,
- ROW_NOT_FOUND
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/dfa19fb30a737fa5.
Report an issue: GitHub.