santifer/career-ops · warning
⚠️ No config/cv-facts.json — forbidden/advisory phrase…
Error message
⚠️ No config/cv-facts.json — forbidden/advisory phrase checks did not run.
What it means
generate-pdf.mjs runs a fact gate via verify-cv-facts.mjs's assertFacts(), which checks the HTML for forbidden and advisory phrases using lists loaded from config/cv-facts.json. When that config file is missing, assertFacts reports configMissing and the phrase checks cannot run — this warning clarifies that any subsequent 'passed' verdict covers metrics/facts only, not phrase screening.
Solutions
- Create config/cv-facts.json with forbidden/advisory phrase lists (even empty lists) to make the gate explicit
- Run from the repository root so the relative config path resolves
- If phrase screening is intentionally not wanted, ignore this warning — the fact/metric checks still ran
Example fix
// before: file missing
$ node generate-pdf.mjs output/cv.html
⚠️ No config/cv-facts.json — forbidden/advisory phrase checks did not run.
// after: minimal config
echo '{"forbidden":[],"advisory":[]}' > config/cv-facts.json
$ node generate-pdf.mjs output/cv.html
✅ Fact check passed Defensive patterns
Strategy: validation
Validate before calling
import { existsSync } from 'fs';
if (!existsSync('config/cv-facts.json')) console.warn('cv-facts.json missing: forbidden/advisory phrase checks will be skipped'); Type guard
const factConfigExists = () => existsSync('config/cv-facts.json') && (() => { try { JSON.parse(readFileSync('config/cv-facts.json','utf8')); return true; } catch { return false; } })(); Prevention
- Create config/cv-facts.json (even with empty lists) so the fact gate is explicit
- Run generate-pdf.mjs from the repo root so relative config paths resolve
- Remember that a 'passed' verdict without the config only covers metrics/facts, not phrase screening
When it happens
Trigger: PDF generation reaches the fact-check step, `await import('./verify-cv-facts.mjs')` succeeds, but assertFacts() returns factCheck.configMissing === true because config/cv-facts.json does not exist in the repo. Emitted at generate-pdf.mjs:1359 before the verdict output.
Common situations: Fresh clone or new project where config/cv-facts.json was never created (it is an optional gate); user deleted the config after too many advisory warnings; running generate-pdf.mjs outside the repo root so the relative path does not resolve.
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.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- ⚠️ config/profile.yml cv.sections lists only
- ⚠️ config/profile.yml cv.sections lists
- ⚠️ config/profile.yml cv.sections lists
- ⚠️ config/profile.yml cv.sections names
- - advisory phrase
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/68f9b29500933c52.
Report an issue: GitHub.
Appendix: source
Thrown at generate-pdf.mjs:1360
// carries the numbers a reader acts on, but the CV path enforced the gate only
// as an instructed step in the mode prompts — so a programmatic caller (a
// bridge, a script, a batch run) rendered inflated metrics in silence. Gate the
// normalized HTML, which is the document that actually prints.
if (!skipFactCheck && cvMarkdown) {
// Imported lazily, INSIDE the guard. A static import is resolved at module
// load whether or not this branch runs, and the page-budget/batch suites copy
// generate-pdf.mjs alone into a temp workspace — a static import of a sibling
// that isn't copied made every one of those suites die with
// ERR_MODULE_NOT_FOUND before reaching the behaviour under test. Those
// fixtures also ship no cv.md, so this branch is never entered there. If the
// module is genuinely missing in a real workspace this throws and the render
// fails, which is the correct direction to fail for a fact gate.
const { assertFacts } = await import('./verify-cv-facts.mjs');
const factCheck = assertFacts(html, { label: basename(inputPath) });
// Ahead of the verdict, because it qualifies it: with no config the phrase
// lists are empty, so a "passed" below covers metrics and facts only.
if (factCheck.configMissing) {
console.warn('⚠️ No config/cv-facts.json — forbidden/advisory phrase checks did not run.');
}
if (factCheck.verdict === 'warn') {
console.warn(`⚠️ CV fact check warning: ${basename(inputPath)}`);
for (const phrase of factCheck.warnings) console.warn(` - advisory phrase: ${phrase}`);
} else {
console.log('✅ Fact check passed');
}
}
return renderHtmlToPdf(html, outputPath, {
format,
baseDir: dirname(inputPath),
reportNum,
inputPath,
maxPages,
strictPages,
styleTokens: readStyleTokens(resolve(workspaceRoot, 'config', 'profile.yml')),
});View on GitHub (pinned to aac998c7ed)