santifer/career-ops · error · Error

Malformed benchmarks file at ${path}: expected a top-level "

Error message

Malformed benchmarks file at ${path}: expected a top-level "benchmarks" map

What it means

The benchmarks YAML loaded successfully but the top-level structure is wrong: doc.benchmarks must be a non-null object (a map of metric keys like response_rate, application_to_interview). This fires when benchmarks is missing, is an array, is a string, or is explicitly null.

Source

Thrown at funnel-velocity.mjs:231

      sameDayExcluded,
      censored,
    };
  }
  return result;
}

// --- Benchmarks ---
export function loadBenchmarks(explicitPath) {
  const path = explicitPath
    || (existsSync(join(CAREER_OPS, 'config/benchmarks.yml')) ? join(CAREER_OPS, 'config/benchmarks.yml') : join(CAREER_OPS, 'templates/benchmarks.yml'));
  let doc;
  try {
    doc = yaml.load(readFileSync(path, 'utf-8'));
  } catch (err) {
    throw new Error(`Cannot read benchmarks at ${path}: ${err.message}`);
  }
  if (!doc || typeof doc.benchmarks !== 'object' || doc.benchmarks === null) {
    throw new Error(`Malformed benchmarks file at ${path}: expected a top-level "benchmarks" map`);
  }
  return { benchmarks: doc.benchmarks, path };
}

/** Classify an own-rate percentage against a benchmark's range (inclusive). */
export function classify(ownPct, metric) {
  if (ownPct === null || ownPct === undefined || !metric || !Array.isArray(metric.range_pct)) return null;
  const [lo, hi] = metric.range_pct;
  const band = ownPct < lo ? 'below-range' : ownPct > hi ? 'above-range' : 'within-range';
  const typical = metric.typical_pct;
  return {
    band,
    ownPct,
    rangePct: [lo, hi],
    typicalPct: typical ?? null,
    vsTypical: typical ? Math.round((ownPct / typical) * 10) / 10 : null,
    source: metric.source ?? null,
    year: metric.year ?? null,

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Ensure the YAML has a top-level benchmarks: key mapping to an object, e.g. benchmarks:\n response_rate:\n range_pct: [2, 13].
  2. Compare against templates/benchmarks.yml for the expected structure.
  3. Validate with node -e "const d=require('js-yaml').load(require('fs').readFileSync('config/benchmarks.yml','utf-8')); console.log(typeof d.benchmarks, d.benchmarks)".

Example fix

# before (malformed)
benchmarks:
  - response_rate
  - application_to_interview

# after
benchmarks:
  response_rate:
    range_pct: [2, 13]
    typical_pct: 3
    year: 2025
  application_to_interview:
    range_pct: [3, 15]
    typical_pct: 5
    year: 2025
Defensive patterns

Strategy: validation

Validate before calling

import * as yaml from 'js-yaml';
import { readFileSync } from 'fs';

function validateBenchmarksShape(path) {
  const doc = yaml.load(readFileSync(path, 'utf-8'));
  if (!doc || typeof doc.benchmarks !== 'object' || doc.benchmarks === null) {
    throw new Error(`Expected top-level "benchmarks" object map in ${path}`);
  }
  return doc.benchmarks;
}

Type guard

/** @param {unknown} doc */
function isBenchmarksDoc(doc) {
  return (
    typeof doc === 'object' && doc !== null &&
    'benchmarks' in doc &&
    typeof doc.benchmarks === 'object' &&
    doc.benchmarks !== null &&
    !Array.isArray(doc.benchmarks)
  );
}

Prevention

When it happens

Trigger: The YAML has a top-level array instead of a map; the key is misspelled (benchmark: instead of benchmarks:); benchmarks: is present but empty/null; indentation nests benchmarks under another key so it is not top-level.

Common situations: User hand-edits config/benchmarks.yml and renames or restructures the key; a YAML indentation error pushes benchmarks under a parent key; the file was replaced with a flat list.

Understand the failure class

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/30ec0427f5b3a927. Report an issue: GitHub.