santifer/career-ops · warning
⚠️ modes_dir " " escapes project root; using default modes/
Error message
⚠️ modes_dir "${customModesDir}" escapes project root; using default modes/ What it means
gemini-eval.mjs reads config/profile.yml and, when language.modes_dir is set, resolves it against CODE_ROOT to find market-specific evaluation modes. This warning means the configured modes_dir resolved outside the project root (a relative path with '..' segments or an absolute path pointing elsewhere). As a safe fallback the tool keeps using the default modes/ directory, so evaluation continues but with default (English) vocabulary instead of the intended market modes.
Solutions
- Edit config/profile.yml so language.modes_dir is a path relative to the project root that stays inside it, e.g. modes/de.
- Remove the leading '/' or '../' segments from modes_dir; if your modes live outside the repo, copy the market dir (e.g. modes/de) into the project.
- Verify the resolved directory contains a market evaluation file (oferta.md, angebot.md, offre.md, kyujin.md, is-ilani.md, or naukri.md) so the next check in the chain passes.
- If you do not need a market mode set, delete the language.modes_dir key to use the default modes/ silently.
Example fix
# before (config/profile.yml) language: output: de modes_dir: /home/user/other-repo/modes/de # after language: output: de modes_dir: modes/de
Defensive patterns
Strategy: validation
Validate before calling
import { resolve, relative, isAbsolute } from 'node:path';
import { existsSync } from 'node:fs';
const md = profile?.language?.modes_dir;
if (md) {
const ok = !isAbsolute(md) && !relative(process.cwd(), resolve(process.cwd(), md)).startsWith('..')
&& existsSync(resolve(process.cwd(), md));
if (!ok) console.warn(`modes_dir "${md}" will be rejected — use a relative in-project path like modes/de`);
} Type guard
function isSafeRelativeModesDir(p) {
return typeof p === 'string' && p.length > 0 && !p.startsWith('/') &&
!p.split(/[\\/]/).includes('..');
} Try / catch
try {
await runEvaluation();
} catch (err) {
console.warn(`Fell back to default modes/ — check language.modes_dir in config/profile.yml: ${err.message}`);
} Prevention
- Always set modes_dir as a project-root-relative path (e.g. modes/de); never absolute or '../' paths.
- After editing config/profile.yml, confirm the warning list is empty by running a quick evaluation.
- Keep market mode directories inside the repository so relative resolution always succeeds.
- Document the required modes_dir format next to the language block in your profile.
When it happens
Trigger: Setting language.modes_dir in config/profile.yml to a path that either is absolute (isAbsolute check) or, after resolve(CODE_ROOT, dir), has a relative() result starting with '..' — e.g. modes_dir: '../shared-modes' or modes_dir: '/home/user/modes/de'.
Common situations: Users copy a modes_dir path from another machine or install location; users set an absolute path like /home/me/career-ops/modes/de while the repo lives elsewhere; users use '../' to point at a sibling checkout; monorepo setups where modes were moved but profile.yml was not updated.
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
- ⚠️ Could not parse config/profile.yml
- arbeitsagentur: entry
- Cannot read benchmarks at
- config/profile.yml is empty or invalid YAML: fill it in…
- ⚠️ Failed to parse profile.yml
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/c753c6e685409a74.
Report an issue: GitHub.
Appendix: source
Thrown at gemini-eval.mjs:113
// Determine the localization modes directory and evaluation filename dynamically from config/profile.yml
let modesDir = 'modes';
let evalFilename = 'oferta.md';
function stripBom(str) {
return str.charCodeAt(0) === 0xFEFF ? str.slice(1) : str;
}
if (existsSync(PATHS.profileYml)) {
try {
const yamlContent = stripBom(readFileSync(PATHS.profileYml, 'utf-8'));
const profile = yaml.load(yamlContent);
if (profile && profile.language && profile.language.modes_dir) {
const customModesDir = profile.language.modes_dir;
const dirPath = resolve(CODE_ROOT, customModesDir);
const rel = relative(CODE_ROOT, dirPath);
if (rel.startsWith('..') || isAbsolute(customModesDir)) {
console.warn(`⚠️ modes_dir "${customModesDir}" escapes project root; using default modes/`);
} else {
if (existsSync(dirPath)) {
const candidateFiles = ['oferta.md', 'angebot.md', 'offre.md', 'kyujin.md', 'is-ilani.md', 'naukri.md'];
const found = candidateFiles.find((file) => existsSync(join(dirPath, file)));
if (found) {
modesDir = customModesDir;
evalFilename = found;
} else {
console.warn(`⚠️ No matching evaluation file found in ${customModesDir}; using default modes/oferta.md`);
}
} else {
console.warn(`⚠️ modes_dir "${customModesDir}" not found; using default modes/`);
}
}
}
} catch (err) {
console.warn(`⚠️ Could not parse config/profile.yml: ${err.message}`);
}View on GitHub (pinned to aac998c7ed)