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
- 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).
- 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.
- Verify the resolved siteId/corpName in cfg.homeUrl actually points at the careersite home and not a corporate landing page.
- 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
- Keep the extractToken regex in sync with the live page shape — verify after Cornerstone upgrades.
- Expect some tenants' WAFs to block datacenter IPs; route via a residential egress or skip those tenants.
- Log the fetched HTML length once on failure to distinguish empty/challenge/shape-change.
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
- csod: cannot resolve careersite URL for ${entry.name}
- echojobs: unexpected API response on page ${page} — expected
- glints: HTTP ${err.status} — ${detail}
- ${msg}
- Could not determine the rendered PDF page count from its pag
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/c3ef00235e940af1.
Report an issue: GitHub.