{"record":{"id":"c753c6e685409a74","repo":"santifer/career-ops","slug":"modes-dir-custommodesdir-escapes-project-root-using-default","errorCode":null,"errorMessage":"⚠️   modes_dir \"${customModesDir}\" escapes project root; using default modes/","messagePattern":"⚠️   modes_dir \"(.+?)\" escapes project root; using default modes/","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"gemini-eval.mjs","lineNumber":113,"sourceCode":"\n// Determine the localization modes directory and evaluation filename dynamically from config/profile.yml\nlet modesDir = 'modes';\nlet evalFilename = 'oferta.md';\n\nfunction stripBom(str) {\n  return str.charCodeAt(0) === 0xFEFF ? str.slice(1) : str;\n}\n\nif (existsSync(PATHS.profileYml)) {\n  try {\n    const yamlContent = stripBom(readFileSync(PATHS.profileYml, 'utf-8'));\n    const profile = yaml.load(yamlContent);\n    if (profile && profile.language && profile.language.modes_dir) {\n      const customModesDir = profile.language.modes_dir;\n      const dirPath = resolve(CODE_ROOT, customModesDir);\n      const rel = relative(CODE_ROOT, dirPath);\n      if (rel.startsWith('..') || isAbsolute(customModesDir)) {\n        console.warn(`⚠️   modes_dir \"${customModesDir}\" escapes project root; using default modes/`);\n      } else {\n        if (existsSync(dirPath)) {\n          const candidateFiles = ['oferta.md', 'angebot.md', 'offre.md', 'kyujin.md', 'is-ilani.md', 'naukri.md'];\n          const found = candidateFiles.find((file) => existsSync(join(dirPath, file)));\n          if (found) {\n            modesDir = customModesDir;\n            evalFilename = found;\n          } else {\n            console.warn(`⚠️   No matching evaluation file found in ${customModesDir}; using default modes/oferta.md`);\n          }\n        } else {\n          console.warn(`⚠️   modes_dir \"${customModesDir}\" not found; using default modes/`);\n        }\n      }\n    }\n  } catch (err) {\n    console.warn(`⚠️   Could not parse config/profile.yml: ${err.message}`);\n  }","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/gemini-eval.mjs#L95-L131","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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.","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."],"exampleFix":"# before (config/profile.yml)\nlanguage:\n  output: de\n  modes_dir: /home/user/other-repo/modes/de\n\n# after\nlanguage:\n  output: de\n  modes_dir: modes/de","handlingStrategy":"validation","validationCode":"import { resolve, relative, isAbsolute } from 'node:path';\nimport { existsSync } from 'node:fs';\nconst md = profile?.language?.modes_dir;\nif (md) {\n  const ok = !isAbsolute(md) && !relative(process.cwd(), resolve(process.cwd(), md)).startsWith('..')\n    && existsSync(resolve(process.cwd(), md));\n  if (!ok) console.warn(`modes_dir \"${md}\" will be rejected — use a relative in-project path like modes/de`);\n}","typeGuard":"function isSafeRelativeModesDir(p) {\n  return typeof p === 'string' && p.length > 0 && !p.startsWith('/') &&\n    !p.split(/[\\\\/]/).includes('..');\n}","tryCatchPattern":"try {\n  await runEvaluation();\n} catch (err) {\n  console.warn(`Fell back to default modes/ — check language.modes_dir in config/profile.yml: ${err.message}`);\n}","preventionTips":["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."],"tags":["config","path","yaml","fallback"],"backgroundTag":"invalid-config-value","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}