santifer/career-ops · error · Error

csod: no anonymous token on ${cfg.homeUrl}

Error message

csod: no anonymous token on ${cfg.homeUrl}

What it means

Thrown by the csod provider after it fetches the careersite bootstrap page (cfg.homeUrl) and extractToken(html) returns '' — the page HTML contained no match for /"token"\s*:\s*"([A-Za-z0-9._-]+)"/. The bootstrap page is supposed to embed an anonymous JWT; without it the subsequent search API call cannot be authorised. This is a runtime/site-state error, not a config error: the URL resolved fine, but the page did not contain what was expected.

Source

Thrown at providers/csod.mjs:200

    // 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' } });
      const setCookies = typeof res?.headers?.getSetCookie === 'function' ? res.headers.getSetCookie() : [];
      cookie = cookieHeaderFrom(setCookies);
      html = await res.text();
    } else {
      html = await ctx.fetchText(cfg.homeUrl, { redirect: 'error', headers: { accept: 'text/html' } });
    }
    const token = extractToken(html);
    if (!token) throw new Error(`csod: no anonymous token on ${cfg.homeUrl}`);

    const wait = (ms) => (ctx.sleep ? ctx.sleep(ms) : new Promise((r) => setTimeout(r, ms)));
    const maxPages = resolveMaxPages(entry);
    const jobs = [];
    const seen = new Set();
    let total = null;

    for (let page = 1; page <= maxPages; page++) {
      if (page > 1) await wait(PAGE_DELAY_MS);
      const json = await ctx.fetchJson(cfg.searchApi, {
        method: 'POST',
        redirect: 'error',
        headers: {
          'content-type': 'application/json',
          accept: 'application/json',
          authorization: `Bearer ${token}`,
          ...(cookie ? { cookie } : {}),
        },

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Manually open cfg.homeUrl in a browser and confirm a "token":"..." literal is present in the HTML; if the shape changed, update the extractToken regex (providers/csod.mjs).
  2. Check the fetched HTML (log it once) for a WAF/login/maintenance page — if so, route the request through a residential IP or a different egress, or add the tenant to a skip list.
  3. Verify the resolved siteId/corpName in cfg.homeUrl actually points at the careersite home and not a corporate landing page.
  4. Retry transient interstitials; treat a persistent failure as a site-incompatibility and disable the entry.

Example fix

// before — token regex misses a moved/renamed field
const m = html.match(/"token"\s*:\s*"([A-Za-z0-9._-]+)"/);

// after — after confirming the new shape on the live page, e.g.
const m = html.match(/"anonymousToken"\s*:\s*"([A-Za-z0-9._-]+)"/);
Defensive patterns

Strategy: try-catch

Validate before calling

// Cannot be fully prevented statically (the page must be fetched). Best-effort pre-flight:
async function csodBootstrapLooksHealthy(ctx, homeUrl) {
  try {
    const html = await ctx.fetchText(homeUrl, { redirect: 'error', headers: { accept: 'text/html' } });
    return /"token"\s*:\s*"[A-Za-z0-9._-]+"/.test(html);
  } catch { return false; }
}

Type guard

// After fetching the bootstrap page, check the token shape before driving the search API.
function htmlHasCsodToken(html) {
  return typeof html === 'string' && /"token"\s*:\s*"[A-Za-z0-9._-]+"/.test(html);
}

Try / catch

try { await csod.fetch(entry, ctx); }
catch (e) {
  if (/^csod: no anonymous token/.test(e.message)) {
    // runtime/site-state: open cfg.homeUrl in a browser; if shape changed, update extractToken regex;
    // if a WAF/challenge page was returned, retry from a different egress or disable the entry
  } else throw e;
}

Prevention

When it happens

Trigger: The bootstrap GET returned an error/interstitial page (WAF challenge, login redirect, maintenance page, empty body); the tenant changed their page structure so the token is no longer in a "token":"..." literal; the wrong siteId was resolved so the page exists but is not the careersite bootstrap; or a network/proxy returned a rewritten body.

Common situations: A tenant's WAF blocking the datacenter egress IP and returning a challenge page instead of the bootstrap HTML; a Cornerstone version upgrade that moved the token into a different JSON shape; a corporate proxy injecting a block page; the siteId in the URL pointing at a non-careersite page.

Related errors


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