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
- Verify the file exists: ls <skill-root>/<relativePath> and correct the path (typically 'SKILL.md' or 'references/<file>.md')
- Check filename casing — paths are case-sensitive on Linux/macOS default filesystems
- Make sure you pass a file, not a directory: append 'SKILL.md' when you just want the skill's main doc
- 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
- Default to 'SKILL.md' when unsure which file to read
- Check filename casing — Linux is case-sensitive
- List the skill directory contents once before scripted reads
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
- File not found: ${path}
- 视频文件不存在: ${videoPath}
- Video file not found: ${resolved}
- Receipt file does not exist: ${receipt}
- ${label} file does not exist: ${ref.value}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/71306f5ffb8e7c74.
Report an issue: GitHub.