santifer/career-ops · error · Error
Fact check failed${options.label ? ` for ${options.label}` :
Error message
Fact check failed${options.label ? ` for ${options.label}` : ''}: ${details.join('; ')} What it means
assertFacts() is the throwing variant of verify-cv-facts.mjs: it re-runs verifyFacts() and, when the verdict is 'block', throws one aggregated error listing metric-like claims absent from sources, unsupported non-metric facts (kind=value for employers/titles/tools), and forbidden phrases found. This is the tool's no-fabrication gate doing its job — the generated document contains claims with no source backing.
Source
Thrown at verify-cv-facts.mjs:416
.filter(phrase => stripMarkup(targetText).toLowerCase().includes(String(phrase).toLowerCase()));
return {
verdict: invented.length || unsupportedFacts.length || forbidden.length ? 'block' : warnings.length ? 'warn' : 'pass',
invented,
unsupportedFacts,
forbidden,
warnings,
};
}
/** Verify a document and throw when it contains a blocking unsupported claim. */
export function assertFacts(targetText, options = {}) {
const result = verifyFacts(targetText, options);
if (result.verdict === 'block') {
const details = [];
if (result.invented.length) details.push(`metric-like claims absent from sources: ${result.invented.join(', ')}`);
if (result.unsupportedFacts.length) details.push(`non-metric facts absent from sources: ${result.unsupportedFacts.map(({ kind, value }) => `${kind}=${value}`).join(', ')}`);
if (result.forbidden.length) details.push(`forbidden phrases found: ${result.forbidden.join(', ')}`);
throw new Error(`Fact check failed${options.label ? ` for ${options.label}` : ''}: ${details.join('; ')}`);
}
return result;
}
/** Parse the fact-validator command-line arguments. */
function parseCliArgs(args) {
const sourcePaths = [];
let targetArg = '';
let configPath = DEFAULT_CONFIG;
let json = false;
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--source' || arg === '--config') {
if (!args[i + 1]) throw new Error(`${arg} requires a path`);
if (arg === '--source') sourcePaths.push(args[++i]);
else configPath = args[++i];
} else if (arg === '--help' || arg === '-h') {
return { help: true };View on GitHub (pinned to 60398d6549)
Solutions
- Read the three detail segments in the message: they name each offending claim, so remove or correct those exact strings in the generated document
- If a claim is genuinely true, add it to a primary source (cv.md / config) and pass that file via --source, then re-run
- If a number is legitimately allowed, add it to allow_metrics in the fact-gate config instead of deleting it silently
- Use the non-throwing verifyFacts() export when you want the verdict object and to decide yourself how to handle 'block'
Example fix
// before
import { assertFacts } from './verify-cv-facts.mjs';
assertFacts(cvText, { sources, label: 'cv' }); // throws on block
// after
import { verifyFacts } from './verify-cv-facts.mjs';
const r = verifyFacts(cvText, { sources, label: 'cv' });
if (r.verdict === 'block') {
console.error('blocking claims:', r.invented, r.unsupportedFacts.map((f) => f.value));
process.exitCode = 1; // or strip the claims and regenerate
} Defensive patterns
Strategy: try-catch
Validate before calling
// Non-throwing pre-check: get the verdict and decide yourself
import { verifyFacts } from './verify-cv-facts.mjs';
const r = verifyFacts(text, { sources, config });
if (r.verdict === 'block') {
console.error('blocking claims:', [...r.invented, ...r.unsupportedFacts.map((f) => `${f.kind}=${f.value}`), ...r.forbidden]);
} Type guard
function isBlockedVerdict(v) {
return v != null && typeof v === 'object' && v.verdict === 'block';
} Try / catch
try {
assertFacts(text, { sources, config, label: 'cv' });
} catch (err) {
if (String(err.message).startsWith('Fact check failed')) {
// parse the three detail segments, strip/correct those claims, regenerate, re-assert
const details = err.message.slice('Fact check failed'.length);
// ... handle, do not swallow: an unhandled block means fabricated content shipped
process.exitCode = 1;
} else {
throw err;
}
} Prevention
- Generate content only from primary sources and re-assert after every edit
- Add genuinely-true quantified claims to cv.md/config (or allow_metrics) instead of bypassing the gate
- Never catch-and-ignore a block verdict — that is the no-fabrication rule failing open
When it happens
Trigger: Calling assertFacts(generatedText, {sources, config, label}) where the text contains a metric like 'increased revenue by 70%' that appears in no --source file, an employer/title/tool claim not present in the sources, or a phrase listed in forbidden_phrases. Severity of unsupported entries decides block vs warn.
Common situations: AI-generated CV/cover-letter text that embellished a number beyond cv.md; tailoring that imported the JD's tool names as the candidate's own; a stale or missing source file (forgetting --source cv.md) so even true claims look unsupported; forbidden-phrase lists catching hype wording.
Related errors
- Unsupported profile photo data URL (expected base64 PNG, JPE
- Unsupported profile photo URL scheme: ${photo.split(':', 1)[
- Unsupported profile photo format: ${photo} (expected PNG, JP
- Profile photo is empty: ${photo}
- Unresolved placeholders: ${[...new Set(unresolved)].join(',
AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20).
Data as JSON: /api/errors/ea9a4f106f0eb1d7.
Report an issue: GitHub.