santifer/career-ops · error · Error

oraclecloud: cannot derive API URL for

Error message

oraclecloud: cannot derive API URL for ${entry.name}

What it means

The Oracle Recruiting provider could not derive an API base site from the entry configuration. fetch() calls resolveSite(entry), which extracts an allowlisted *.fa.*.oraclecloud*.com host from the entry (careers_url or explicit config); when it returns null the provider has nothing to call and throws instead of guessing. Unlike error 360 this is usually a missing/empty config value, not an untrusted one.

Solutions

  1. Add or correct careers_url in the portals.yml entry so it points to the tenant's *.fa.[region.][ocs.]oraclecloud[1-99].com host.
  2. Check that the entry's provider id is actually oraclecloud and not a mis-assigned provider for a different ATS.
  3. If the board is only reachable via a vanity domain, set the explicit Oracle tenant host in the entry config that resolveSite reads.
  4. Use audit-portals.mjs on the entry to confirm which provider actually claims that careers_url before keeping it under oraclecloud.

Example fix

// before (portals.yml)
- name: Acme
  provider: oraclecloud
// after
- name: Acme
  provider: oraclecloud
  careers_url: https://acme.fa.ocs.oraclecloud.com/hcmUI/CandidateExperience/en/sites/CX_1
Defensive patterns

Strategy: validation

Validate before calling

function hasDerivableOracleSite(entry) {
  const raw = typeof entry.careers_url === 'string' ? entry.careers_url : '';
  if (!raw) return false;
  try {
    const { hostname } = new URL(raw);
    return /^[a-z0-9-]+\.fa\.(?:[a-z0-9-]+\.)?(?:ocs\.)?oraclecloud(?:[1-9][0-9]?)?\.com$/i.test(hostname);
  } catch { return false; }
}
if (!hasDerivableOracleSite(entry)) throw new Error(`oraclecloud: entry ${entry.name} needs an allowlisted careers_url`);

Type guard

function hasOracleCareersUrl(entry) {
  return entry != null && typeof entry === 'object' &&
    typeof entry.careers_url === 'string' && entry.careers_url.length > 0 &&
    (() => { try { return isOracleUrl(entry.careers_url); } catch { return false; } })();
}

Try / catch

try {
  await oracleProvider.fetch(entry, ctx);
} catch (e) {
  if (String(e.message).startsWith('oraclecloud: cannot derive API URL')) {
    logger.warn({ entry: entry.name }, 'no usable Oracle host in entry config — skipping');
    return null;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling providers/oraclecloud.mjs fetch(entry, ctx) when entry has no careers_url, careers_url is not a string, the URL is malformed, or resolveSite's regex loop matches no allowlisted hostname — e.g. an oraclecloud entry whose URL points to a Greenhouse/Ashby domain.

Common situations: A portals.yml entry tagged with the wrong provider id (company actually uses another ATS); an entry added with only a name and no careers_url; or careers_url pointing at a redirect vanity domain that no provider claims.

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

Appendix: source

Thrown at providers/oraclecloud.mjs:233

  return out;
}

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

  detect(entry) {
    try {
      const site = resolveSite(entry);
      return site ? { url: buildApiUrl(site, 0, PAGE_SIZE) } : null;
    } catch {
      return null;
    }
  },

  async fetch(entry, ctx) {
    const site = resolveSite(entry);
    if (!site) throw new Error(`oraclecloud: cannot derive API URL for ${entry.name}`);

    const maxPages = Number.isInteger(entry.max_pages) && entry.max_pages > 0
      ? Math.min(entry.max_pages, MAX_PAGES)
      : MAX_PAGES;

    const all = [];
    let total = null;
    for (let page = 0; page < maxPages; page++) {
      const offset = page * PAGE_SIZE;
      const apiUrl = buildApiUrl(site, offset, PAGE_SIZE);
      assertOracleUrl(apiUrl); // SSRF guard before every fetch
      if (page > 0) await sleep(INTER_PAGE_DELAY_MS, ctx);

      const json = await fetchJsonWithRetry(ctx, apiUrl, {
        redirect: 'error',
        headers: { 'User-Agent': BROWSER_LIKE_USER_AGENT, Accept: 'application/json' },
      }, RETRY_POLICY);

View on GitHub (pinned to e7abd431fc)