santifer/career-ops · error · Error
${key} must be an array in ${path}
Error message
${key} must be an array in ${path} What it means
verify-cv-facts.mjs's loadConfig() validates the optional fact-gate JSON: each of the four keys (allow_metrics, allow_facts, forbidden_phrases, warn_phrases) may be absent or null (defaulted to []) but must be an array if present. A scalar, string, or object value throws this error naming the offending key and file.
Source
Thrown at verify-cv-facts.mjs:354
export function auditClaims(targetText, sourceText, config = {}) {
const allowed = allowedMetricSet(sourceText, config.allow_metrics);
const invented = [...metricClaims(targetText)].filter(claim => !allowed.has(claim));
// Hoisted: stripMarkup re-ran the whole markup pass once per configured
// phrase (CodeRabbit, reviewing #2175). Same result, one pass.
const targetPlain = stripMarkup(targetText).toLowerCase();
const forbidden = (config.forbidden_phrases || [])
.filter(Boolean)
.filter(phrase => targetPlain.includes(String(phrase).toLowerCase()));
return { invented, forbidden };
}
/** Load and validate the optional fact-gate configuration file. */
function loadConfig(path) {
if (!existsSync(path)) return { allow_metrics: [], allow_facts: [], forbidden_phrases: [], warn_phrases: [] };
const config = JSON.parse(readFileSync(path, 'utf-8'));
for (const key of ['allow_metrics', 'allow_facts', 'forbidden_phrases', 'warn_phrases']) {
if (config[key] == null) config[key] = [];
else if (!Array.isArray(config[key])) throw new Error(`${key} must be an array in ${path}`);
}
return config;
}
/** Resolve a CLI or configuration path relative to the selected working directory. */
function resolveInputPath(path, cwd = process.cwd()) {
return isAbsolute(path) ? path : join(cwd, path);
}
/** Check a normalized fact as a complete token or phrase, not a substring. */
function sourceContainsFact(sourceText, value) {
const escaped = value
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
.replace(/\s+/g, '\\s+');
return new RegExp(`(?:^|[^\\p{L}\\p{N}+#/-])${escaped}(?=$|[^\\p{L}\\p{N}+#/-])`, 'iu').test(sourceText);
}
/**View on GitHub (pinned to 60398d6549)
Solutions
- Open the path shown in the message and wrap the named key's value in a JSON array
- Re-run `node verify-cv-facts.mjs --self-test` or your document check to confirm the config now loads
- If the config is optional for you, move/rename it to fall back to the all-empty default (all lists, no gating)
Example fix
// before (my-gate.json)
{
"allow_metrics": "40%",
"forbidden_phrases": "guaranteed"
}
// after
{
"allow_metrics": ["40%"],
"forbidden_phrases": ["guaranteed"]
} Defensive patterns
Strategy: validation
Validate before calling
const raw = JSON.parse(readFileSync(configPath, 'utf-8'));
for (const key of ['allow_metrics', 'allow_facts', 'forbidden_phrases', 'warn_phrases']) {
if (raw[key] != null && !Array.isArray(raw[key])) {
console.error(`${key} must be an array — fix ${configPath} before running`);
process.exit(1);
}
} Type guard
function isFactGateConfig(v) {
if (v == null || typeof v !== 'object') return false;
return ['allow_metrics', 'allow_facts', 'forbidden_phrases', 'warn_phrases']
.every((k) => v[k] == null || Array.isArray(v[k]));
} Try / catch
try {
const config = loadConfig(path);
} catch (err) {
if (/must be an array in/.test(err.message)) {
// config shape problem: edit the named key, then re-run
console.error(err.message);
process.exit(1);
}
throw err; // JSON.parse syntax errors surface here too — fix the JSON first
} Prevention
- Always wrap fact-gate lists in JSON arrays, even single-entry lists
- Validate edited configs with a JSON schema check in your editor or a one-liner before running
- Absent keys default to []; omit rather than null/fill with empty strings
When it happens
Trigger: Running `node verify-cv-facts.mjs <doc> --config my-gate.json` (or using the DEFAULT_CONFIG file) where e.g. `"allow_metrics": "40%"` or `"forbidden_phrases": {"a": 1}` instead of `["40%"]` / `["a"]`. JSON.parse succeeded; only the shape check failed.
Common situations: Hand-editing the fact-gate config and quoting a single value instead of wrapping it in an array; merging configs where a list got collapsed to a string; YAML-vs-JSON confusion producing an object.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- Missing required field: ${context}.${key}
- Invalid page budget "${maxPages}". Use a positive integer.
- each entry needs a string "input" and "output"
- Invalid URL: ${url}
- the release pointer is not an object: ${url}
AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20).
Data as JSON: /api/errors/ec7567adc2e4d282.
Report an issue: GitHub.