{"record":{"id":"d1d901524cc3cde6","repo":"santifer/career-ops","slug":"portals-yml-e-name-is-enabled-but-no-provider-claims-e","errorCode":null,"errorMessage":"portals.yml: \"${e.name}\" is enabled but no provider claims ${e.careers_url || 'its careers_url'} — scan.mjs skips it on every run without naming it (run node audit-portals.mjs)","messagePattern":"portals\\.yml: \"(.+?)\" is enabled but no provider claims (.+?) — scan\\.mjs skips it on every run without naming it \\(run node audit-portals\\.mjs\\)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"verify-pipeline.mjs","lineNumber":570,"sourceCode":"    await mergeProviderPlugins(providers, { root: CODE_ROOT });\n    const { silent, handoff, unknownProvider } = findUnclaimedEntries(entries, providers);\n\n    // findUnclaimedEntries silently skips an entry with no (or blank) `name` —\n    // it can't report what it can't label. Without this, that entry vanishes\n    // from silent/handoff/unknownProvider entirely, and the enabled count\n    // below (which doesn't share the same eligibility rule) would still\n    // include it — so a malformed entry never gets a provider check AND the\n    // \"All N entries resolve\" success line claims it as resolved anyway.\n    const malformed = entries.filter(e => e && e.enabled !== false && (typeof e.name !== 'string' || !e.name.trim()));\n    for (const e of malformed) {\n      warn(`portals.yml: an enabled entry has no name (careers_url: ${e.careers_url || 'none'}) — it cannot be provider-checked; give it a name`);\n    }\n\n    for (const e of unknownProvider) {\n      error(`portals.yml: \"${e.name}\" sets an unknown provider — ${e.error}. The entry never scans (see providers/ for valid ids)`);\n    }\n    for (const e of silent) {\n      warn(`portals.yml: \"${e.name}\" is enabled but no provider claims ${e.careers_url || 'its careers_url'} — scan.mjs skips it on every run without naming it (run node audit-portals.mjs)`);\n    }\n    if (silent.length === 0 && unknownProvider.length === 0 && malformed.length === 0) {\n      const enabled = entries.filter(e => e && e.enabled !== false).length;\n      ok(handoff.length > 0\n        ? `All ${enabled - handoff.length} scannable portals.yml entries resolve to a provider (${handoff.length} on websearch handoff)`\n        : `All ${enabled} enabled portals.yml entries resolve to a provider`);\n    }\n  } catch (err) {\n    warn(`Portal coverage check could not run: ${err.message}`);\n  }\n}\n\n// --- Check 16: invisible control bytes already in tracker cells (#3892) ---\n// cell() in tracker-utils.mjs strips these on the way in, which stops new ones\n// entering but can do nothing about the ones already written. This is the only\n// place such a byte is visible at all: it shifts or truncates the positional\n// `split('|')` parse, so a row silently reads as a different row or drops out\n// of a count entirely, while every renderer of the table — markdown, GitHub,","sourceCodeStart":552,"sourceCodeEnd":588,"githubUrl":"https://github.com/santifer/career-ops/blob/e7abd431fce9348a95261acac9e0c14779c35df8/verify-pipeline.mjs#L552-L588","documentation":"verify-pipeline.mjs Check 15 warns about portals.yml 'silent' entries: enabled, named, but no provider module in providers/ claims the entry's careers_url. scan.mjs skips such entries on every run without any message, so the board reads as covered while contributing zero postings. The audit companion is audit-portals.mjs.","triggerScenarios":"Running `node verify-pipeline.mjs` when an enabled portals.yml entry's careers_url doesn't match any known ATS URL pattern (Greenhouse/Lever/Ashby/Workday/iCIMS) or lacks a recognized provider field — typically after adding a company whose careers page is a custom portal.","commonSituations":"Adding a company with a generic careers site URL instead of its ATS board URL; ATS migration changed the board host so the pattern no longer matches; typos in careers_url.","solutions":["Run `node audit-portals.mjs` to see what the URL actually serves and whether any provider claims it","Replace careers_url with the company's real ATS board URL (e.g. boards.greenhouse.io/<tenant>)","If no ATS board exists, either disable the entry (enabled: false) or switch it to a websearch-handoff entry","Re-run verify-pipeline.mjs and confirm the entry now appears in the 'resolve to a provider' count"],"exampleFix":"# before (custom careers page, never scanned)\n- name: Acme\n  careers_url: https://acme.com/careers\n# after (real ATS board)\n- name: Acme\n  careers_url: https://boards.greenhouse.io/acme\n  provider: greenhouse","handlingStrategy":"validation","validationCode":"const KNOWN = /boards\\.greenhouse\\.io|jobs\\.lever\\.co|jobs\\.ashbyhq\\.com|.*\\.wd\\d*\\.myworkdayjobs\\.com|careers-.*\\.icims\\.com/;\nfor (const e of entries.filter(e => e && e.enabled !== false)) {\n  if (!KNOWN.test(e.careers_url || '')) console.warn(`no provider claims ${e.name}: ${e.careers_url}`);\n}","typeGuard":"const isProviderBacked = (e) => e && KNOWN_ATS_HOSTS.some(h => (e.careers_url || '').includes(h));","tryCatchPattern":null,"preventionTips":["Run audit-portals.mjs after adding companies","Link the ATS board URL, not the generic careers page","Disable entries whose company has no ATS board","Re-baseline audit snapshots to catch ATS migrations"],"tags":["config","scanner","coverage"],"backgroundTag":"unsupported-config-value","analyzedSha":"e7abd431fce9348a95261acac9e0c14779c35df8","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}