{"record":{"id":"64f136911629919c","repo":"santifer/career-ops","slug":"hecklerkoch-cannot-resolve-vacancy-list-for-ent","errorCode":null,"errorMessage":"hecklerkoch: cannot resolve vacancy list for ${entry.name}","messagePattern":"hecklerkoch: cannot resolve vacancy list for (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/hecklerkoch.mjs","lineNumber":86,"sourceCode":"    seen.add(id);\n    out.push({ id, title, url });\n  }\n  return out;\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'hecklerkoch',\n\n  detect(entry) {\n    const url = entry.api || entry.careers_url || '';\n    if (typeof url !== 'string') return null;\n    return resolveListUrl({ api: url }) ? { url } : null;\n  },\n\n  async fetch(entry, ctx) {\n    const listUrl = resolveListUrl(entry);\n    if (!listUrl) throw new Error(`hecklerkoch: cannot resolve vacancy list for ${entry.name}`);\n    const html = await ctx.fetchText(listUrl, { headers: { accept: 'text/html' } });\n    const rows = parseListing(html);\n    const jobs = [];\n    for (const row of rows) {\n      jobs.push({ title: row.title, url: row.url, company: entry.name, location: '' });\n      if (jobs.length >= MAX_JOBS) break;\n    }\n    return jobs;\n  },\n};\n","sourceCodeStart":68,"sourceCodeEnd":97,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/hecklerkoch.mjs#L68-L97","documentation":"Thrown by hecklerkoch fetch() when resolveListUrl(entry) returns null. resolveListUrl accepts only http(s) URLs whose host is exactly heckler-koch.com or a *.heckler-koch.com subdomain, and returns null for any other host, a non-URL string, or a non-http(s) scheme. detect() returns null silently for the same condition; fetch() turns it into a hard error.","triggerScenarios":"An entry tagged provider: hecklerkoch whose api/careers_url points at a different host (e.g. the karriere.heckler-koch.com apply backend, which is a subdomain but the path may not be a Stellenangebote page — note resolveListUrl still accepts the subdomain and rewrites the path, so this is fine; the real trigger is a wholly different host); a careers_url that is not a string; a URL with an unsupported scheme like ftp.","commonSituations":"Wiring the apply-backend URL (karriere.heckler-koch.com) as careers_url expecting it to list jobs — it won't, but resolveListUrl still rewrites it to the Stellenangebote path so this should pass; pasting a third-party recruiter link; setting provider: hecklerkoch on a generic company that is not H&K.","solutions":["Set careers_url (or api) to https://heckler-koch.com/de/Karriere/Stellenangebote or any https://*.heckler-koch.com URL.","If the entry is not actually Heckler & Koch, remove provider: hecklerkoch and let detect() route it to the right provider.","Confirm with detect(): if provider.detect(entry) returns null, fetch() will throw this too."],"exampleFix":"# before\n- name: Heckler & Koch\n  provider: hecklerkoch\n  careers_url: https://www.indeed.com/jobs?q=hk\n\n# after\n- name: Heckler & Koch\n  provider: hecklerkoch\n  careers_url: https://heckler-koch.com/de/Karriere/Stellenangebote","handlingStrategy":"validation","validationCode":"const hit = hecklerkochProvider.detect(entry);\nif (!hit) {\n  console.warn(`hecklerkoch: ${entry.name} — careers_url must be on heckler-koch.com or a subdomain`);\n  continue;\n}\nconst jobs = await hecklerkochProvider.fetch(entry, ctx);","typeGuard":"/** True when the entry can resolve an H&K vacancy list URL. */\nfunction isHecklerkochDerivable(entry) {\n  const raw = entry?.api || entry?.careers_url;\n  if (typeof raw !== 'string') return false;\n  try {\n    const u = new URL(raw);\n    const host = u.host.toLowerCase();\n    return (u.protocol === 'https:' || u.protocol === 'http:')\n      && (host === 'heckler-koch.com' || host.endsWith('.heckler-koch.com'));\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  const jobs = await hecklerkochProvider.fetch(entry, ctx);\n} catch (err) {\n  if (/cannot resolve vacancy list/.test(err.message)) {\n    console.error(`config: ${entry.name} — ${err.message}`);\n  } else {\n    throw err;\n  }\n}","preventionTips":["Only point the hecklerkoch provider at heckler-koch.com / *.heckler-koch.com URLs.","Use detect() as the entry gate — it mirrors resolveListUrl's accept criteria exactly.","Do not tag non-H&K companies with provider: hecklerkoch."],"tags":["config","url-resolution","hecklerkoch","ats"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}