jackwener/OpenCLI · error · ArgumentError

Skill file not found: ${name}/${relativePath}

Error message

Skill file not found: ${name}/${relativePath}

What it means

readOpenCliSkill checks fs.existsSync and that the target is a regular file before reading. If the resolved path inside the skill does not exist or is a directory, it throws ArgumentError (exit code 2) telling you the skill file was not found and that 'opencli skills list <skill>' is not supported — read SKILL.md or a known references/... file.

Source

Thrown at src/skills.ts:63

  const { name, pathInSkill } = parseSkillTarget(target, relpath);
  if (!name.startsWith('opencli-')) {
    throw new ArgumentError(`Unknown OpenCLI skill: ${name}`, 'Run "opencli skills list" to see available OpenCLI skills.');
  }

  const skillsRoot = getSkillsRoot(packageRoot);
  const skillRoot = path.join(skillsRoot, name);
  if (!isDirectory(skillRoot) || !fs.existsSync(path.join(skillRoot, 'SKILL.md'))) {
    throw new ArgumentError(`Unknown OpenCLI skill: ${name}`, 'Run "opencli skills list" to see available OpenCLI skills.');
  }

  const relativePath = normalizeSkillPath(pathInSkill || 'SKILL.md');
  const absolutePath = path.resolve(skillRoot, relativePath);
  const relativeToRoot = path.relative(skillRoot, absolutePath);
  if (relativeToRoot.startsWith('..') || path.isAbsolute(relativeToRoot)) {
    throw new ArgumentError(`Invalid skill path: ${relativePath}`, 'Skill paths must stay inside the selected OpenCLI skill.');
  }
  if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isFile()) {
    throw new ArgumentError(`Skill file not found: ${name}/${relativePath}`, 'Run "opencli skills list <skill>" is not supported yet; read SKILL.md or a known references/... file.');
  }

  return {
    skill: name,
    path: relativePath,
    content: fs.readFileSync(absolutePath, 'utf8'),
  };
}

function readSkillInfo(skillsRoot: string, name: string): OpenCliSkillInfo | null {
  const skillMdPath = path.join(skillsRoot, name, 'SKILL.md');
  if (!fs.existsSync(skillMdPath)) return null;
  const content = fs.readFileSync(skillMdPath, 'utf8');
  const fm = parseFrontmatter(content);
  return {
    name: typeof fm.name === 'string' && fm.name ? fm.name : name,
    description: typeof fm.description === 'string' ? fm.description : firstBodyParagraph(content),
    version: typeof fm.version === 'string' || typeof fm.version === 'number' ? String(fm.version) : '',

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Verify the file exists: ls <skill-root>/<relativePath> and correct the path (typically 'SKILL.md' or 'references/<file>.md')
  2. Check filename casing — paths are case-sensitive on Linux/macOS default filesystems
  3. Make sure you pass a file, not a directory: append 'SKILL.md' when you just want the skill's main doc
  4. Reinstall/update the skill if the file was removed in a newer version

Example fix

// before
await readOpenCliSkill('grok', 'api');
// after
await readOpenCliSkill('grok', 'references/api.md');
Defensive patterns

Strategy: validation

Validate before calling

const abs = path.resolve(skillRoot, relativePath);
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) throw new Error(`skill file missing: ${relativePath}`);

Try / catch

try {
  const skill = await readOpenCliSkill(name, p);
} catch (e) {
  if (e instanceof ArgumentError && /not found/i.test(e.message)) {
    console.error(`'${p}' not found in skill '${name}'. Read SKILL.md or a references/ file.`);
  } else throw e;
}

Prevention

When it happens

Trigger: Requesting a file path inside a skill that does not exist on disk (wrong filename, wrong casing, file lives under references/ but path omits that), or passing a directory path instead of a file (e.g. just the skill name with no 'SKILL.md' when the default resolution misses).

Common situations: Case-sensitive filesystems (Linux) vs. skill docs written with different casing; skill versions that renamed reference files; typos like 'SKILL.md ' with trailing space; passing 'references' (a directory) instead of a file inside it.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/71306f5ffb8e7c74. Report an issue: GitHub.