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
- Remove the `provider: echojobs` entry from portals.yml — this is the intended and only resolution.
- Consult docs/SUPPORTED_JOB_BOARDS.md for currently supported boards as a replacement source.
- 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).
- 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
- Remove `provider: echojobs` from portals.yml immediately — the throw is unconditional.
- Check docs/SUPPORTED_JOB_BOARDS.md before adding any provider to config.
- Prefer direct ATS providers (greenhouse/lever/ashby) over third-party aggregate feeds, which rot faster.
- Treat repeated retirement messages in scan logs as config debt to clean, not errors to suppress.
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
- apify: entry has invalid field_map. Each of title, url…
- apify: entry missing 'actor' (e.g. misceres/indeed-scraper)
- APIFY_TOKEN not set — enable apify in config/plugins.yml…
- arbeitnow: invalid URL
- arbeitsagentur: entry
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)