santifer/career-ops · error · Error
portals.yml not found
Error message
portals.yml not found
What it means
Thrown by parsePortals() when readFile('portals.yml') returns empty/falsy (file missing, empty, or unreadable as text). parsePortals is the runner's no-CLI scanner config source, mirroring scan.mjs's schema; without it the runner cannot know which companies to scan.
Source
Thrown at openrouter-runner.mjs:454
}
}
// ---------------------------------------------------------------------------
// portals.yml parser — reads the canonical schema with js-yaml (same library and
// field names as scan.mjs: `title_filter.positive/negative` + `tracked_companies`),
// so it never drifts from the main scanner. The runner's no-CLI scan path covers
// companies that expose a direct JSON `api:`; careers_url-only / Playwright /
// search-query companies are handled by the full /career-ops scan pipeline.
// `rawOverride` lets tests feed YAML text directly (see test-all.mjs drift guard).
// ---------------------------------------------------------------------------
function normKeywords(v) {
if (!Array.isArray(v)) return [];
return v.map(x => String(x ?? '').toLowerCase().trim()).filter(Boolean);
}
export function parsePortals(rawOverride) {
const raw = rawOverride ?? readFile('portals.yml');
if (!raw) throw new Error('portals.yml not found');
const config = yaml.load(raw) || {};
const tf = config.title_filter || {};
const positive = normKeywords(tf.positive);
const negative = normKeywords(tf.negative);
function titleMatches(title) {
const t = String(title ?? '').toLowerCase();
return positive.some(k => t.includes(k)) && !negative.some(k => t.includes(k));
}
// Companies with a direct JSON `api:` endpoint (the no-CLI scan path).
const tracked = Array.isArray(config.tracked_companies) ? config.tracked_companies : [];
const companies = tracked
.filter(c => c && c.api && c.enabled !== false)
.map(c => ({ name: String(c.name ?? c.company ?? 'Unknown'), api: String(c.api).trim() }));
return { companies, titleMatches };
}View on GitHub (pinned to 9b17a8ac97)
Solutions
- Run onboarding (node doctor.mjs) to scaffold portals.yml from templates/portals.example.yml.
- If the file exists but is empty, copy templates/portals.example.yml over it and edit.
- Run the runner from the repo root so the relative 'portals.yml' path resolves.
- Pass a rawOverride string to parsePortals() for tests instead of relying on the file.
Example fix
// before: portals.yml missing -> throws
const cfg = parsePortals();
// after: copy the template first, then parse
fs.copyFileSync('templates/portals.example.yml', 'portals.yml');
const cfg = parsePortals(); Defensive patterns
Strategy: validation
Validate before calling
import { existsSync, statSync } from 'fs';
function portalsReadable() {
if (!existsSync('portals.yml')) return false;
return statSync('portals.yml').size > 0;
}
if (!portalsReadable()) {
// scaffold from template before calling parsePortals()
} Type guard
null
Try / catch
try {
const cfg = parsePortals();
} catch (e) {
if (/portals\.yml not found/.test(e.message)) {
// run onboarding / copy template
} else throw e;
} Prevention
- Run onboarding (node doctor.mjs) before any scan/eval on a fresh clone.
- Always run the runner from the repo root.
- For tests, call parsePortals(rawOverride) with YAML text instead of relying on the file.
When it happens
Trigger: First run before onboarding completed; portals.yml deleted or never created; the file exists but is empty; running the runner from a working directory that isn't the repo root so the relative path resolves to nothing.
Common situations: New clone without running onboarding; portals.yml was gitignored locally and not regenerated; user ran `node openrouter-runner.mjs` from a subdirectory; the file was truncated to 0 bytes by a bad editor save.
Related errors
- ROW_NOT_FOUND
- vdab: entry "${entry.name || '(unnamed)'}" has no vdab.keywo
- Unsupported profile photo style: ${style} (expected rounded,
- Cannot read benchmarks at ${path}: ${err.message}
- Malformed benchmarks file at ${path}: expected a top-level "
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/70b90d632be0c9f9.
Report an issue: GitHub.