santifer/career-ops · error

Malformed benchmarks file at

Error message

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

What it means

loadBenchmarks successfully parsed the YAML but the resulting document does not contain a non-null top-level `benchmarks` map. The function requires doc.benchmarks to be an object; an empty file, a bare scalar, an array, or a document keyed differently all fail this structural check and throw with the file path.

Solutions

  1. Open the file at the printed path and add/restore a top-level `benchmarks:` map.
  2. Restore the canonical shape from templates/benchmarks.yml in the repo.
  3. If the file was meant as config for another tool, move it; benchmarks.yml must key everything under `benchmarks:`.

Example fix

# before (malformed)
funnel:
  applied_to_own: [10, 25]
# after
benchmarks:
  applied_to_own: [10, 25]
Defensive patterns

Strategy: validation

Validate before calling

import {readFileSync} from 'fs'; import yaml from 'js-yaml';
const doc = yaml.load(readFileSync('templates/benchmarks.yml','utf8'));
if (!doc || typeof doc.benchmarks !== 'object' || doc.benchmarks === null) throw new Error('benchmarks.yml must have a top-level "benchmarks" map');

Type guard

const hasBenchmarks = (d) => !!d && typeof d === 'object' && !Array.isArray(d) && typeof d.benchmarks === 'object' && d.benchmarks !== null;

Try / catch

try { loadBenchmarks(); } catch (e) { if (/Malformed benchmarks/.test(e.message)) { restoreTemplateAndRetry(); } else throw e; }

Prevention

When it happens

Trigger: benchmarks.yml exists but is empty; parses to null/a string/a list; or uses a top-level key other than `benchmarks` (e.g. `funnel:` or content nested under a comment header).

Common situations: A user rewrote the file from scratch guessing the format; a truncated or templated file with only comments; copying an unrelated YAML into config/benchmarks.yml.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/30ec0427f5b3a927. Report an issue: GitHub.

Appendix: source

Thrown at funnel-velocity.mjs:259

      sameDayExcluded,
      censored,
    };
  }
  return result;
}

// --- Benchmarks ---
export function loadBenchmarks(explicitPath) {
  const path = explicitPath
    || (existsSync(join(DATA_ROOT, 'config/benchmarks.yml')) ? join(DATA_ROOT, 'config/benchmarks.yml') : join(CODE_ROOT, '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 aac998c7ed)