santifer/career-ops · error

RETIRED_MESSAGE

Error message

RETIRED_MESSAGE

What it means

The EchoJobs provider is permanently retired: the board-wide public feed at https://echojobs.io/api/jobs now answers with an HTTP 429 Vercel bot-protection checkpoint and the site's robots.txt disallows /api. Since career-ops does not work around bot protection, fetch() deliberately makes no network call and throws this explanatory message immediately. detect() is intentionally left matching `provider: echojobs` so stale configurations surface this clear message instead of a silent no-provider match.

Solutions

  1. Remove the `provider: echojobs` entry from portals.yml — this is the intended and only resolution.
  2. Consult docs/SUPPORTED_JOB_BOARDS.md for currently supported boards as a replacement source.
  3. If you need those listings, use the underlying ATS providers directly (job URLs in the old feed pointed at the companies' own Greenhouse/Lever/Ashby boards).
  4. Do not attempt to proxy or spoof the feed — the retirement is deliberate policy, not a fixable outage.

Example fix

// before (portals.yml)
- name: echojobs
  provider: echojobs
// after — entry removed entirely
// (optionally replace with direct ATS entries, e.g. greenhouse/lever/ashby providers)
Defensive patterns

Strategy: validation

Validate before calling

// guard before configuring or calling
if (entry?.provider === 'echojobs') {
  throw new Error('echojobs provider is retired (feed behind bot protection since 2026-08) — remove the entry from portals.yml');
}

Try / catch

try {
  await provider.fetch(entry, ctx);
} catch (e) {
  if (e.message.includes('echojobs: this feed is gone')) {
    // expected retirement — remove the entry, no retry or fallback is meaningful
    console.warn('Removing retired echojobs entry from scan rotation');
  } else throw e;
}

Prevention

When it happens

Trigger: Any call to fetch() on the echojobs provider — i.e. a portals.yml entry still carrying `provider: echojobs`. The throw is unconditional; there is no input, network state, or configuration that avoids it.

Common situations: A portals.yml entry for EchoJobs left in place after the feed went dark (retired 2026-08-20, #2976); a scan run over an uncleaned config; a tutorial or template that still recommends echojobs as a provider.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at providers/echojobs.mjs:138

  // `posted_at` is already epoch milliseconds.
  if (Number.isFinite(j.posted_at) && j.posted_at > 0) job.postedAt = j.posted_at;
  return job;
}

/** @type {Provider} */
export default {
  id: 'echojobs',

  detect(entry) {
    return entry?.provider === 'echojobs' ? { url: FEED_BASE } : null;
  },

  async fetch() {
    // Deliberately no network call: the feed is confirmed gone (see the file
    // header), and career-ops does not work around bot protection. Throwing
    // immediately, with a message naming the cause, is what turns "expected
    // { jobs: [...] }" into something a user can act on.
    throw new Error(RETIRED_MESSAGE);
  },
};

View on GitHub (pinned to aac998c7ed)