santifer/career-ops · error · Error

mycareersfuture: all

Error message

mycareersfuture: all ${keywords.length} keyword request(s) failed — ${errors[0]}

What it means

mycareersfuture fetch() runs one request per keyword and collects results; it distinguishes a legitimately empty search from a total outage. If zero keyword requests succeeded and at least one recorded an error, it throws this error summarizing the count and the first underlying error. Partial success (any keyword succeeding) never throws — only a full failure does.

Solutions

  1. Read errors[0] from the message to identify the root cause (DNS vs 403 vs 5xx).
  2. Verify general connectivity to https://www.mycareersfuture.gov.sg from the machine running the scan (curl the endpoint).
  3. If blocked/rate-limited, retry later with a lower request rate or from a different network/IP.
  4. If the API endpoint changed, update the base URL/request construction in providers/mycareersfuture.mjs and re-run; if only some keywords failed, note no action is needed since partial results are returned.

Example fix

// before
const jobs = await provider.fetch(entry, ctx); // throws on total outage
// after
let jobs;
try {
  jobs = await provider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).includes('keyword request(s) failed')) {
    console.warn('mycareersfuture unreachable, skipping this run');
    jobs = [];
  } else throw err;
}
Defensive patterns

Strategy: retry

Validate before calling

if (!keywords.length) throw new Error('no keywords configured — fix config before scanning');

Try / catch

try {
  jobs = await provider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).includes('keyword request(s) failed')) {
    console.warn(`mycareersfuture outage (${err.message}); retrying in 60s`);
    await sleep(60_000);
    jobs = await provider.fetch(entry, ctx).catch(() => []);
  } else throw err;
}

Prevention

When it happens

Trigger: Every keyword request to the MyCareersFuture API fails — network down, DNS failure, the API endpoint moved or returns 5xx/403 for all queries, TLS interception, or a bot-protection blanket block — so succeeded===0 and errors[0] exists.

Common situations: Corporate proxy/firewall blocking the API; MyCareersFuture API downtime or endpoint/shape change; being IP-rate-limited or bot-blocked (all requests 403); no internet connectivity during the scan; an expired API base URL in the provider after a site migration.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/eefe74fd801df705. Report an issue: GitHub.

Appendix: source

Thrown at providers/mycareersfuture.mjs:218

        raw = await fetchKeyword(keyword);
        succeeded++;
      } catch (err) {
        if (probing) throw err;
        // Recall-first: tolerate a single failed keyword and keep going.
        errors.push(`"${keyword}": ${(err && err.message) || err}`);
        continue;
      }
      for (const r of raw) {
        const job = normalizeJob(r);
        if (job && !byId.has(job.id)) byId.set(job.id, job);
      }
    }

    // Total outage = every keyword request failed. A keyword that answered
    // with zero results is not an outage, so key off the success count, not
    // the deduped result size — otherwise a legitimately-empty search throws.
    if (succeeded === 0 && errors.length) {
      throw new Error(`mycareersfuture: all ${keywords.length} keyword request(s) failed — ${errors[0]}`);
    }

    return [...byId.values()].map(({ id, ...job }) => job);
  },
};

View on GitHub (pinned to aac998c7ed)