santifer/career-ops · warning

Portal coverage check could not run

Error message

Portal coverage check could not run: ${err.message}

What it means

verify-pipeline.mjs check 15 audits which enabled portals.yml entries resolve to an ATS provider. When the coverage audit itself throws (bad config load, fetch failure, parse error), it is caught and downgraded to this warning instead of failing the whole health check.

Solutions

  1. Read err.message in the warning to identify the root cause (parse error vs fetch error)
  2. Validate portals.yml parses: node -e "import('js-yaml').then(y=>y.load(require('fs').readFileSync('portals.yml','utf8')))" or run the project's config check
  3. If it is a fetch failure, re-run when network is available — the check is deliberately non-fatal
  4. Audit the entry flagged by audit-portals.mjs to confirm its provider/careers_url fields

Example fix

// before: portals.yml
- name: Acme
  careers_url: greenhouse/acme
  enabled: true
// after (fetch-failure case): just re-run online
node verify-pipeline.mjs
// or disable the broken entry temporarily
- name: Acme
  careers_url: greenhouse/acme
  enabled: false
Defensive patterns

Strategy: fallback

Validate before calling

import { readFileSync } from 'fs';
import { load } from 'js-yaml';
const cfg = load(readFileSync('portals.yml', 'utf8'));
const bad = cfg.filter(e => e.enabled !== false && !e.careers_url);
if (bad.length) console.warn('entries missing careers_url:', bad.map(e => e.name));

Type guard

const isEnabledEntry = (e) => Boolean(e && typeof e === 'object' && typeof e.careers_url === 'string' && e.enabled !== false);

Try / catch

try {
  await runPortalCoverageCheck();
} catch (err) {
  console.warn(`Portal coverage check could not run: ${err.message}`); // non-fatal by design
}

Prevention

When it happens

Trigger: The try block wrapping the portal-coverage audit throws — e.g. portals.yml is unparseable, an entry lacks expected fields, or the provider-resolution helper rejects (network error fetching board data). The catch at verify-pipeline.mjs:569 emits warn(`Portal coverage check could not run: ${err.message}`).

Common situations: portals.yml edited by hand with a YAML typo; offline run of a check that fetches live board data; an enabled entry with a malformed careers_url; provider module change breaking resolution.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at verify-pipeline.mjs:579

    const malformed = entries.filter(e => e && e.enabled !== false && (typeof e.name !== 'string' || !e.name.trim()));
    for (const e of malformed) {
      warn(`portals.yml: an enabled entry has no name (careers_url: ${e.careers_url || 'none'}) — it cannot be provider-checked; give it a name`);
    }

    for (const e of unknownProvider) {
      error(`portals.yml: "${e.name}" sets an unknown provider — ${e.error}. The entry never scans (see providers/ for valid ids)`);
    }
    for (const e of silent) {
      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)`);
    }
    if (silent.length === 0 && unknownProvider.length === 0 && malformed.length === 0) {
      const enabled = entries.filter(e => e && e.enabled !== false).length;
      ok(handoff.length > 0
        ? `All ${enabled - handoff.length} scannable portals.yml entries resolve to a provider (${handoff.length} on websearch handoff)`
        : `All ${enabled} enabled portals.yml entries resolve to a provider`);
    }
  } catch (err) {
    warn(`Portal coverage check could not run: ${err.message}`);
  }
}

// --- Check 16: invisible control bytes already in tracker cells (#3892) ---
// cell() in tracker-utils.mjs strips these on the way in, which stops new ones
// entering but can do nothing about the ones already written. This is the only
// place such a byte is visible at all: it shifts or truncates the positional
// `split('|')` parse, so a row silently reads as a different row or drops out
// of a count entirely, while every renderer of the table — markdown, GitHub,
// the web dashboard — shows the cell as correct. The corruption surfaces much
// later as an unrelated arithmetic discrepancy with no trail back to the cause.
//
// Read off the RAW lines rather than the parsed `entries`, so a byte in a
// column this file has no field for is caught too, and reported with the file
// line number: a shifted parse is exactly the situation where the row's own #
// cell is the thing not to trust.
//
// CONTROL_CHARS is imported, never re-declared — a second copy of the range

View on GitHub (pinned to e7abd431fc)