santifer/career-ops · error · Error

csod: cannot resolve careersite URL for ${entry.name}

Error message

csod: cannot resolve careersite URL for ${entry.name}

What it means

Thrown by the csod (Cornerstone OnDemand) provider's fetch() when resolveConfig(entry) returns null. resolveConfig needs an https URL whose host is exactly csod.com or ends in .csod.com, AND whose path carries the careersite shape /ux/ats/careersite/{digits}/ (a numeric siteId). Both the branded corporate page (careers_url) and the csod.com URL (api:) follow this convention. detect() applies the same check, so this fires only when fetch() is driven on a non-detecting entry.

Source

Thrown at providers/csod.mjs:174

  if (Number.isInteger(v) && v > 0) return Math.min(v, MAX_PAGES);
  return MAX_PAGES;
}

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

  detect(entry) {
    const url = entry.api || entry.careers_url || '';
    if (typeof url !== 'string') return null;
    // Host check (not a path substring) so evil.com/x.csod.com can't spoof it,
    // and the URL must carry the careersite path shape we know how to drive.
    return resolveConfig({ api: url }) ? { url } : null;
  },

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

    // The bootstrap page yields two things, not one: the anonymous bearer
    // token, and — on some tenants — the session cookies the search API
    // insists on. careers-kln rejects an otherwise valid token+body with
    // "HTTP 401 CSOD Unauthorized" until those cookies come back with it, so
    // the token alone is not a sufficient credential. Prefer ctx.fetchResponse
    // to see Set-Cookie; fall back to fetchText when the caller's ctx predates
    // it (older embedders and test mocks), which keeps the pre-cookie
    // behaviour intact for tenants that never needed it.
    //
    // cfg.homeUrl and cfg.searchApi are both built from the same parsed
    // origin, so replaying these cookies cannot reach a third-party host.
    // redirect:'error' on the bootstrap keeps that true: origin validation
    // covers the URL we ask for, not wherever a 3xx would send us.
    let html;
    let cookie = '';
    if (typeof ctx.fetchResponse === 'function') {
      const res = await ctx.fetchResponse(cfg.homeUrl, { redirect: 'error', headers: { accept: 'text/html' } });

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Set api: to the full csod.com careersite URL, e.g. https://career-ohb.csod.com/ux/ats/careersite/4/home?c=career-ohb.
  2. Ensure the path contains /ux/ats/careersite/<numeric siteId>/ — the regex extracts the siteId from there.
  3. Keep the branded page in careers_url and the csod.com URL in api: (same convention as workday/successfactors).
  4. Gate with provider.detect(entry) before fetch().

Example fix

# before — branded host only, no csod.com URL
- name: OHB
  provider: csod
  careers_url: https://www.career-ohb.com

# after — csod.com careersite URL with numeric siteId
- name: OHB
  provider: csod
  careers_url: https://www.career-ohb.com
  api: https://career-ohb.csod.com/ux/ats/careersite/4/home?c=career-ohb
Defensive patterns

Strategy: validation

Validate before calling

import csod from './providers/csod.mjs';
if (!csod.detect(entry)) {
  // entry.api/entry.careers_url is not https://*.csod.com/ux/ats/careersite/<id>/... — fix config
}

Type guard

/** True when entry resolves to a CSOD careersite (https *.csod.com with /ux/ats/careersite/<digits>/). */
function isCsodEntry(entry) {
  const raw = typeof entry?.api === 'string' ? entry.api : (typeof entry?.careers_url === 'string' ? entry.careers_url : '');
  if (!raw) return false;
  let u;
  try { u = new URL(raw); } catch { return false; }
  if (u.protocol !== 'https:') return false;
  const host = u.host.toLowerCase();
  if (host !== 'csod.com' && !host.endsWith('.csod.com')) return false;
  return /\/ux\/ats\/careersite\/\d+/.test(u.pathname);
}

Try / catch

try { await csod.fetch(entry, ctx); }
catch (e) {
  if (/^csod: cannot resolve careersite URL/.test(e.message)) {
    // config issue — set api: to the csod.com careersite URL; do not retry
  } else throw e;
}

Prevention

When it happens

Trigger: entry.api/entry.careers_url is missing, unparseable, non-https, not on *.csod.com, or its path lacks a numeric /ux/ats/careersite/{siteId}/ segment.

Common situations: Putting the branded corporate page (not on csod.com) in api:; using an http URL (rejected because session cookies would travel in clear); a URL whose siteId path was copied without the /ux/ats/careersite/ prefix; omitting the csod.com URL entirely.

Related errors


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