santifer/career-ops · error · Error
API error: ${json.errorMsg || json.errorCode || 'success=fal
Error message
API error: ${json.errorMsg || json.errorCode || 'success=false'} What it means
The Alibaba careers provider POSTs to Alibaba's internal job-board API carrying a scraped CSRF token (XSRF-TOKEN cookie + x-xsrf-token header) and a body built from `buildBody(keyword, page)`. Alibaba reports failures in-band with HTTP 200 (a `success: false` body) rather than via status codes, so the provider checks `json?.success === false` and throws using `errorMsg || errorCode || 'success=false'`. This prevents a dead board from reading as an empty-but-alive one.
Source
Thrown at providers/alibaba.mjs:157
for (let page = 1; page <= maxPages; page++) {
if (firstRequest) firstRequest = false;
else await sleep(INTER_PAGE_DELAY_MS);
let json;
try {
json = /** @type {any} */ (await ctx.fetchJson(API, {
method: 'POST',
headers: {
'content-type': 'application/json',
'cookie': `XSRF-TOKEN=${csrfToken}`,
'x-xsrf-token': csrfToken,
},
body: buildBody(keyword, page),
redirect: 'error',
}));
// The API reports failures in-band with HTTP 200; surface them so a
// dead board doesn't read as an empty-but-alive one.
if (json?.success === false) {
throw new Error(`API error: ${json.errorMsg || json.errorCode || 'success=false'}`);
}
} catch (err) {
// A dead board should still read as a failure, but a mid-run blip
// must not discard what's already collected (same idiom as
// workday/jobstreet/glints). Track successes directly — a keyword
// can legitimately match 0 jobs, so seen.size is not the signal.
if (!succeededOnce) throw err;
console.error(` ⚠ alibaba: keyword "${keyword}" page ${page} failed (${err.message}) — keeping the ${seen.size} jobs collected so far`);
return [...seen.values()];
}
succeededOnce = true;
const { jobs, total } = parseAlibabaResponse(json, entry.name || '阿里巴巴');
if (jobs.length === 0) break;
for (const job of jobs) {
if (!seen.has(job.url)) seen.set(job.url, job);
}
View on GitHub (pinned to 9b17a8ac97)
Solutions
- Read the embedded `errorMsg`/`errorCode` in the thrown message — it is the board's own failure reason and pinpoints the cause.
- Verify the CSRF token acquisition step still produces a fresh, non-empty token before the POST loop.
- Reproduce the POST manually (same cookie + x-xsrf-token header + body) to see the raw in-band error.
- If the failure is intermittent, rely on the existing partial-result behaviour: the provider keeps jobs collected before the blip, so re-run after the board recovers.
- If the API shape changed (no `success` field at all), update the success check in providers/alibaba.mjs:157 to match the new contract.
Example fix
// before
if (json?.success === false) {
throw new Error(`API error: ${json.errorMsg || json.errorCode || 'success=false'}`);
}
// after — also surface a non-200 status and log token presence for diagnostics
if (json?.success === false) {
throw new Error(`API error: ${json.errorMsg || json.errorCode || 'success=false'} (csrf=${csrfToken ? 'present' : 'missing'})`);
} Defensive patterns
Strategy: retry
Validate before calling
// Ensure the CSRF token is fresh and the keyword is non-empty before the POST loop
if (!csrfToken || typeof csrfToken !== 'string') {
throw new Error('alibaba: missing CSRF token — cannot POST');
}
if (!keyword || typeof keyword !== 'string') {
throw new Error(`alibaba: invalid keyword for entry ${entry.name}`);
} Type guard
/** @param {any} j */
function isAlibabaFailure(j) {
return j !== null && typeof j === 'object' && j.success === false;
} Try / catch
try {
const json = await ctx.fetchJson(url, opts);
if (json?.success === false) throw new Error(`API error: ${json.errorMsg || json.errorCode || 'success=false'}`);
succeededOnce = true;
// ...process jobs
} catch (err) {
if (!succeededOnce) throw err; // hard fail before any success
console.error(`⚠ alibaba: ${err.message} — keeping ${seen.size} jobs`);
return [...seen.values()]; // partial result on mid-run blip
} Prevention
- Refresh the CSRF token immediately before the POST loop rather than caching it across runs.
- Treat a `success:false` body as transient first (retry with backoff) before surfacing it as fatal.
- Monitor the partial-result warning log to detect boards that are degrading but not fully dead.
When it happens
Trigger: After `ctx.fetchJson(...)` returns a JSON object whose `success` field is strictly `false`. The thrown message embeds `json.errorMsg`, falling back to `json.errorCode`, then the literal 'success=false'. Only fires before `succeededOnce` is set; a later failure is swallowed and the run returns the jobs collected so far.
Common situations: Expired or stale CSRF token (the token is derived/scraped upstream and can rotate), Alibaba board maintenance or rate-limiting returning a structured error, a malformed/unsupported keyword the API rejects, or an upstream API contract change adding a new failure shape.
Related errors
- jobvite: could not find companyEId on ${boardUrl} for ${entr
- join: __NEXT_DATA__ not found or unexpected structure
- joinup: ${entry.name} page is missing __NEXT_DATA__ (structu
- joinup: ${entry.name} failed to parse __NEXT_DATA__ — ${err.
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/3abad676a8fc1770.
Report an issue: GitHub.