santifer/career-ops · error · Error

portals file not found

Error message

portals file not found: ${filePath}

What it means

audit-portals.mjs's loadCompanies() reads tracked_companies from a portals YAML file and throws when the file does not exist on disk. The audit cannot run without portal configuration, so the library fails immediately with the exact path it looked for, rather than silently auditing zero companies.

Solutions

  1. Create the portals file: copy templates/portals.example.yml to portals.yml in the repo root (or run the career-ops onboarding that does it).
  2. If using --portals, verify the exact path exists: `ls <path>` — check for typos and pass an absolute path when not at repo root.
  3. Run the command from the repository root so the default DEFAULT_PORTALS_PATH resolves.
  4. If a custom data root is intended, set CAREER_OPS_ROOT/CAREER_OPS_DATA_DIR to the tree that actually contains the portals file.

Example fix

// before (fresh clone, no portals.yml)
node audit-portals.mjs
// Error: portals file not found: portals.yml
// after
cp templates/portals.example.yml portals.yml
node audit-portals.mjs
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync } from 'node:fs';
const portalsPath = process.env.CAREER_OPS_PORTALS ?? 'portals.yml';
if (!existsSync(portalsPath)) {
  console.error(`Create ${portalsPath} first: cp templates/portals.example.yml portals.yml`);
  process.exit(1);
}

Type guard

const portalsFileExists = (p) => { try { return existsSync(p) && statSync(p).isFile(); } catch { return false; } };

Try / catch

try {
  const companies = loadCompanies(portalsPath);
} catch (err) {
  if (err.message.startsWith('portals file not found:')) {
    const path = err.message.replace('portals file not found: ', '');
    console.error(`No portals config at ${path}. Copy templates/portals.example.yml there or fix --portals.`);
  } else throw err;
}

Prevention

When it happens

Trigger: Running `node audit-portals.mjs` before portals.yml has been created (repo not onboarded); passing --portals with a typo'd or wrong path; running from a working directory where the default DEFAULT_PORTALS_PATH resolves elsewhere; the file having been renamed, moved, or gitignored-and-never-restored on a fresh clone.

Common situations: Fresh clone of career-ops where templates/portals.example.yml was never copied to portals.yml; typos like `--portals portal.yml`; running the script from a different directory with a relative --portals path; CAREER_OPS_ROOT pointing at the wrong tree so the default path resolves to a nonexistent location.

Understand the failure class

Background: "Config file not found": what it means and how to fix it in docker-sync, Maven, Vagrant, Turborepo and other tools — this error's family across 60 libraries.

Related errors


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

Appendix: source

Thrown at audit-portals.mjs:316

    // exactly the way scan.mjs does.
    const resolved = providers && providers.size > 0 ? resolveProvider(entry, providers) : null;
    if (resolved?.provider) continue;
    const row = {
      name: entry.name,
      scanMethod: entry.scan_method || null,
      careers_url: typeof entry.careers_url === 'string' ? entry.careers_url : '',
      error: resolved?.error || null,
    };
    if (row.error) unknownProvider.push(row);
    else if (row.scanMethod) handoff.push(row);
    else silent.push(row);
  }
  return { silent, handoff, unknownProvider };
}

/** Read tracked_companies out of a portals file. Throws on a missing file. */
export function loadCompanies(filePath = DEFAULT_PORTALS_PATH) {
  if (!existsSync(filePath)) throw new Error(`portals file not found: ${filePath}`);
  const cfg = yaml.load(readFileSync(filePath, 'utf-8')) || {};
  return Array.isArray(cfg.tracked_companies) ? cfg.tracked_companies : [];
}

const ICON = { ok: '✅', small: '🟡', empty: '⚪', 'no-provider': '🚨', error: '❌' };

const KNOWN_FLAGS = [
  '--summary', '--json', '--strict', '--company', '--file',
  '--baseline', '--small-threshold', '--help', '-h',
];
const VALUE_FLAGS = ['--company', '--file', '--baseline', '--small-threshold'];

const USAGE = `Usage:
  node audit-portals.mjs                       # audit every enabled company
  node audit-portals.mjs --summary             # one line per company
  node audit-portals.mjs --json                # machine-readable, for --baseline
  node audit-portals.mjs --company Adyen       # audit a single company
  node audit-portals.mjs --file <path>         # use a specific portals file

View on GitHub (pinned to e7abd431fc)