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

  1. Open the path shown in the message and wrap the named key's value in a JSON array
  2. Re-run `node verify-cv-facts.mjs --self-test` or your document check to confirm the config now loads
  3. 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

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


AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20). Data as JSON: /api/errors/ec7567adc2e4d282. Report an issue: GitHub.