santifer/career-ops · critical

Cannot read benchmarks at

Error message

Cannot read benchmarks at ${path}: ${err.message}

What it means

funnel-velocity.mjs's loadBenchmarks reads the funnel benchmarks YAML (config/benchmarks.yml in DATA_ROOT, falling back to templates/benchmarks.yml in the code root). If readFileSync or yaml.load throws for any reason (missing file, bad permissions, invalid YAML syntax), it wraps the underlying error message and rethrows with the resolved path so you know exactly which file failed.

Solutions

  1. Check the path printed in the error exists and is readable: `ls -l <path>`.
  2. Restore the stock file: `git checkout -- templates/benchmarks.yml` or copy it from the repo template.
  3. Validate the YAML syntax and fix indentation/tabs/unclosed quotes.
  4. If relying on DATA_ROOT/config/benchmarks.yml, confirm the env override resolves where you expect.

Example fix

// before (missing file)
const path = join(DATA_ROOT, 'config/benchmarks.yml'); // not present
// after
git checkout -- templates/benchmarks.yml  // or copy templates/benchmarks.yml into DATA_ROOT/config/
Defensive patterns

Strategy: try-catch

Validate before calling

import {existsSync, accessSync, constants} from 'fs';
const p = existsSync('config/benchmarks.yml') ? 'config/benchmarks.yml' : 'templates/benchmarks.yml';
try { accessSync(p, constants.R_OK); } catch { console.error(`benchmarks file missing/unreadable: ${p}`); process.exit(1); }

Try / catch

try { const { benchmarks } = loadBenchmarks(); } catch (e) { if (/Cannot read benchmarks/.test(e.message)) { restoreFromTemplate(); } else throw e; }

Prevention

When it happens

Trigger: Running funnel-velocity.mjs when neither config/benchmarks.yml nor templates/benchmarks.yml exists at the resolved locations; the file is unreadable due to permissions; or the file contains YAML syntax errors (tabs, bad indentation, unclosed quotes).

Common situations: A repo checkout missing templates/; DATA_ROOT pointed at a directory never initialized with a benchmarks.yml; a user hand-edited the YAML and broke its syntax; a wrong CAREER_OPS_DATA_DIR override resolving to an empty directory.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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

Appendix: source

Thrown at funnel-velocity.mjs:256

      median: days.length >= HOP_MIN_N ? median(days) : null,
      p75: days.length >= HOP_MIN_N ? p75(days) : null,
      insufficientData: days.length < HOP_MIN_N,
      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,

View on GitHub (pinned to aac998c7ed)