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
- Check the path printed in the error exists and is readable: `ls -l <path>`.
- Restore the stock file: `git checkout -- templates/benchmarks.yml` or copy it from the repo template.
- Validate the YAML syntax and fix indentation/tabs/unclosed quotes.
- 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
- Keep templates/benchmarks.yml in the repo and never delete it.
- Validate YAML before saving hand edits.
- Check that CAREER_OPS_DATA_DIR / DATA_ROOT overrides point to an initialized directory.
- Read the resolved path from the error message first — it names the exact failing file.
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
- arbeitsagentur: entry
- config/profile.yml is empty or invalid YAML: fill it in…
- ⚠️ Could not parse config/profile.yml
- ⚠️ Failed to parse profile.yml
- Failed to read profile file at
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)