google-gemini/gemini-cli · error
No valid skills found in "${sourcePath}". Ensure a SKILL.md
Error message
No valid skills found in "${sourcePath}". Ensure a SKILL.md file exists with valid frontmatter. What it means
Thrown by the skill installer when it walks the resolved source directory and `loadSkillsFromDir` returns zero usable skills. A 'valid skill' requires a SKILL.md file with well-formed frontmatter (typically a `name` and `description`). The error is a hard precondition gate — the rest of the linking flow never runs.
Source
Thrown at packages/cli/src/utils/skillUtils.ts:226
/**
* Central logic for linking a skill from a local path via symlink.
*/
export async function linkSkill(
source: string,
scope: 'user' | 'workspace',
onLog: (msg: string) => void,
requestConsent: (
skills: SkillDefinition[],
targetDir: string,
) => Promise<boolean> = () => Promise.resolve(true),
): Promise<Array<{ name: string; location: string }>> {
const sourcePath = path.resolve(source);
onLog(`Searching for skills in ${sourcePath}...`);
const skills = await loadSkillsFromDir(sourcePath);
if (skills.length === 0) {
throw new Error(
`No valid skills found in "${sourcePath}". Ensure a SKILL.md file exists with valid frontmatter.`,
);
}
// Check for internal name collisions
const seenNames = new Map<string, string>();
for (const skill of skills) {
if (seenNames.has(skill.name)) {
throw new Error(
`Duplicate skill name "${skill.name}" found at multiple locations:\n - ${seenNames.get(skill.name)}\n - ${skill.location}`,
);
}
seenNames.set(skill.name, skill.location);
}
const workspaceDir = process.cwd();
const storage = new Storage(workspaceDir);
const targetDir =View on GitHub (pinned to 5024443c72)
Solutions
- Verify the directory actually contains a SKILL.md file: `ls <sourcePath>` and check the exact filename casing.
- Open SKILL.md and confirm the frontmatter block is delimited by `---` lines and contains required keys (at minimum `name` and `description`).
- Point `source` at the directory that holds the skill folder(s), not at a deeper file or a parent that lacks SKILL.md.
- If loading from a remote/zip source, confirm extraction completed and produced the expected SKILL.md files before calling link.
Example fix
// before
await linkSkills({ source: './my-tool' }); // my-tool has no SKILL.md
// after
await linkSkills({ source: './my-tool/skills/my-skill' }); // folder contains SKILL.md with frontmatter Defensive patterns
Strategy: validation
Validate before calling
import { pathExists, readFileSync } from 'fs-extra/esm';
import yaml from 'js-yaml';
async function assertValidSkillDir(sourcePath: string) {
const skillFile = path.join(sourcePath, 'SKILL.md');
if (!(await pathExists(skillFile))) {
throw new Error(`No SKILL.md at ${skillFile}`);
}
const text = readFileSync(skillFile, 'utf8');
const m = text.match(/^---\n([\s\S]*?)\n---/);
if (!m) throw new Error('SKILL.md is missing frontmatter delimiters');
const fm = yaml.load(m[1]) as Record<string, unknown>;
if (!fm.name || !fm.description) {
throw new Error('SKILL.md frontmatter needs name and description');
}
return fm;
}
// before linkSkills:
await assertValidSkillDir(path.resolve(source)); Type guard
function hasValidSkillFrontmatter(text: string): boolean {
const m = text.match(/^---\n([\s\S]*?)\n---/);
if (!m) return false;
try {
const fm = JSON.parse(
// crude yaml-as-json check; use a real parser in production
m[1]
.replace(/^(\w+):/gm, '"$1":')
.replace(/'/g, '"'),
);
return typeof fm.name === 'string' && typeof fm.description === 'string';
} catch {
return false;
}
} Try / catch
try {
await linkSkills(source, scope, onLog, consent);
} catch (e) {
if (e instanceof Error && e.message.startsWith('No valid skills found')) {
onLog(`Skipping ${source}: ${e.message}`);
return [];
}
throw e;
} Prevention
- Run a SKILL.md linter in CI that checks frontmatter presence and required keys before publishing a skill pack.
- Use a skill scaffolding template (`gemini skill create`) so frontmatter is always well-formed.
- Treat an empty `loadSkillsFromDir` result as a build failure in tests, not just a runtime warning.
When it happens
Trigger: Calling the link/install entry point with `source` pointing at a directory that has no SKILL.md, has SKILL.md without frontmatter, or has SKILL.md with malformed YAML frontmatter so every candidate fails parsing. Also triggered when `source` is a single skill file path rather than a directory containing skills, or the directory is empty.
Common situations: Wrong path passed (e.g. parent of the skills folder instead of the folder itself); SKILL.md created without the leading `---` frontmatter delimiters; a required frontmatter key like `name` or `description` is missing; the source folder was generated by a tool that writes markdown without frontmatter; casing typos like `skill.md` instead of `SKILL.md` on case-sensitive filesystems.
Related errors
- Duplicate skill name "${skill.name}" found at multiple locat
- Workspace path ${resolvedPath} does not exist
- Workspace path ${resolvedPath} is not a directory
- The path "${trimmedPath}" is not a directory.
- The path "${trimmedPath}" does not exist.
AI-assisted analysis of google-gemini/gemini-cli@5024443c72 (2026-08-12).
Data as JSON: /api/errors/7770befe9c0fef75.
Report an issue: GitHub.