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

  1. Edit config/profile.yml so language.modes_dir is a path relative to the project root that stays inside it, e.g. modes/de.
  2. 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.
  3. 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.
  4. 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

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


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)