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
- 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).
- If using --portals, verify the exact path exists: `ls <path>` — check for typos and pass an absolute path when not at repo root.
- Run the command from the repository root so the default DEFAULT_PORTALS_PATH resolves.
- 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
- Complete career-ops onboarding (portals.yml) before running audit/scan tooling.
- Run scripts from the repository root, or pass absolute --portals paths.
- Double-check --portals spelling and that the file was not renamed or left un-restored after a clone.
- Verify a custom CAREER_OPS_ROOT actually contains the portals file.
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
- portals.yml not found
- arbeitsagentur: entry
- Cannot read benchmarks at
- config/profile.yml is empty or invalid YAML: fill it in…
- config/profile.yml not found: fill it in first
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 fileView on GitHub (pinned to e7abd431fc)