santifer/career-ops · error · Error
rheinmetall: cannot resolve vacancies URL for ${entry.name}
Error message
rheinmetall: cannot resolve vacancies URL for ${entry.name} What it means
fetch() throws when resolveListUrl(entry) returns null. resolveListUrl reads entry.api || entry.careers_url, parses it, and requires the host to be exactly 'rheinmetall.com' or end with '.rheinmetall.com'; it then normalizes the path to /career/vacancies or /en/career/vacancies. A non-rheinmetall host, an unparseable URL, or a missing URL field all return null.
Source
Thrown at providers/rheinmetall.mjs:102
function resolveMaxPages(entry) {
const v = entry?.max_pages;
if (Number.isInteger(v) && v > 0) return Math.min(v, MAX_PAGES);
return MAX_PAGES;
}
/** @type {Provider} */
export default {
id: 'rheinmetall',
detect(entry) {
const url = entry.api || entry.careers_url || '';
if (typeof url !== 'string') return null;
return resolveListUrl({ api: url }) ? { url } : null;
},
async fetch(entry, ctx) {
const listUrl = resolveListUrl(entry);
if (!listUrl) throw new Error(`rheinmetall: cannot resolve vacancies URL for ${entry.name}`);
const origin = new URL(listUrl).origin;
const wait = (ms) => (ctx.sleep ? ctx.sleep(ms) : new Promise((r) => setTimeout(r, ms)));
const maxPages = resolveMaxPages(entry);
const jobs = [];
const seen = new Set();
for (let page = 1; page <= maxPages; page++) {
if (page > 1) await wait(PAGE_DELAY_MS);
const html = await ctx.fetchText(`${listUrl}?page=${page}`, {
headers: { accept: 'text/html' },
});
const rows = parseVacancies(html, origin);
if (rows.length === 0) {
if (page === 1) console.warn(`rheinmetall: page 1 returned no vacancy cards for ${entry.name} — markup may have changed`);
break; // past the last page
}
View on GitHub (pinned to 9b17a8ac97)
Solutions
- Ensure entry.api or entry.careers_url is a valid https://*.rheinmetall.com URL (e.g. https://www.rheinmetall.com/en/career/vacancies).
- Confirm the host is rheinmetall.com or a direct subdomain — third-party hosted boards will not resolve.
- Call detect(entry) first; it returns null when resolveListUrl cannot produce a URL.
- Check the config for a typo'd hostname (rheinmetal.com without the double-l).
Example fix
# before - name: Rheinmetall provider: rheinmetall # after - name: Rheinmetall provider: rheinmetall api: https://www.rheinmetall.com/en/career/vacancies
Defensive patterns
Strategy: validation
Validate before calling
function isRheinmetallUrl(raw) {
try {
const u = new URL(raw);
const h = u.host.toLowerCase();
return h === 'rheinmetall.com' || h.endsWith('.rheinmetall.com');
} catch { return false; }
}
if (!isRheinmetallUrl(entry.api || entry.careers_url || '')) {
console.warn(`skip ${entry.name}: not a *.rheinmetall.com URL`);
} Type guard
null
Try / catch
try {
await provider.fetch(entry, ctx);
} catch (e) {
if (/cannot resolve vacancies URL/.test(e.message)) {
console.warn(`[skip] ${entry.name}: needs a rheinmetall.com api/careers_url`);
} else throw e;
} Prevention
- Set entry.api or entry.careers_url to a valid https://*.rheinmetall.com URL.
- Call detect(entry) before fetch; it returns null when the host check fails.
- Subsidiaries on non-rheinmetall domains need a different provider.
When it happens
Trigger: entry.api and entry.careers_url are both missing/empty; the URL host is not rheinmetall.com or a subdomain (e.g. a third-party ATS URL); the URL string is malformed and throws inside new URL().
Common situations: A portals.yml row for a Rheinmetall subsidiary points at a non-rheinmetall.com careers domain; the entry was misclassified; the URL field was dropped during data migration.
Related errors
- pinpoint: cannot derive API URL for ${entry.name}
- radancy: cannot resolve search-jobs URL for ${entry.name}
- recruitee: cannot derive API URL for ${entry.name}
- rippling: cannot derive API URL for ${entry.name}
- jobvite: untrusted hostname "${parsed.hostname}" — must be $
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/665b366c86594726.
Report an issue: GitHub.