{"record":{"id":"eefe74fd801df705","repo":"santifer/career-ops","slug":"mycareersfuture-all-keywords-length-keyword-re","errorCode":null,"errorMessage":"mycareersfuture: all ${keywords.length} keyword request(s) failed — ${errors[0]}","messagePattern":"mycareersfuture: all (.+?) keyword request\\(s\\) failed — (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/mycareersfuture.mjs","lineNumber":218,"sourceCode":"        raw = await fetchKeyword(keyword);\n        succeeded++;\n      } catch (err) {\n        if (probing) throw err;\n        // Recall-first: tolerate a single failed keyword and keep going.\n        errors.push(`\"${keyword}\": ${(err && err.message) || err}`);\n        continue;\n      }\n      for (const r of raw) {\n        const job = normalizeJob(r);\n        if (job && !byId.has(job.id)) byId.set(job.id, job);\n      }\n    }\n\n    // Total outage = every keyword request failed. A keyword that answered\n    // with zero results is not an outage, so key off the success count, not\n    // the deduped result size — otherwise a legitimately-empty search throws.\n    if (succeeded === 0 && errors.length) {\n      throw new Error(`mycareersfuture: all ${keywords.length} keyword request(s) failed — ${errors[0]}`);\n    }\n\n    return [...byId.values()].map(({ id, ...job }) => job);\n  },\n};\n","sourceCodeStart":200,"sourceCodeEnd":224,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/mycareersfuture.mjs#L200-L224","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read errors[0] from the message to identify the root cause (DNS vs 403 vs 5xx).","Verify general connectivity to https://www.mycareersfuture.gov.sg from the machine running the scan (curl the endpoint).","If blocked/rate-limited, retry later with a lower request rate or from a different network/IP.","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."],"exampleFix":"// before\nconst jobs = await provider.fetch(entry, ctx); // throws on total outage\n// after\nlet jobs;\ntry {\n  jobs = await provider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).includes('keyword request(s) failed')) {\n    console.warn('mycareersfuture unreachable, skipping this run');\n    jobs = [];\n  } else throw err;\n}","handlingStrategy":"retry","validationCode":"if (!keywords.length) throw new Error('no keywords configured — fix config before scanning');","typeGuard":null,"tryCatchPattern":"try {\n  jobs = await provider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).includes('keyword request(s) failed')) {\n    console.warn(`mycareersfuture outage (${err.message}); retrying in 60s`);\n    await sleep(60_000);\n    jobs = await provider.fetch(entry, ctx).catch(() => []);\n  } else throw err;\n}","preventionTips":["Curl the MyCareersFuture API once before a scan to confirm reachability from the scan machine.","Add exponential backoff and a bounded retry around full-scan fetches.","Reduce per-run request rate if 403/429 responses appear — blanket blocks cause total outages.","Schedule scans to tolerate transient downtime: treat total-outage runs as skipped, not failed pipelines."],"tags":["network","api","outage","mycareersfuture"],"backgroundTag":"network-request-failed","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}