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

  1. Read the embedded `errorMsg`/`errorCode` in the thrown message — it is the board's own failure reason and pinpoints the cause.
  2. Verify the CSRF token acquisition step still produces a fresh, non-empty token before the POST loop.
  3. Reproduce the POST manually (same cookie + x-xsrf-token header + body) to see the raw in-band error.
  4. 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.
  5. 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

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


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