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

  1. Verify the directory actually contains a SKILL.md file: `ls <sourcePath>` and check the exact filename casing.
  2. Open SKILL.md and confirm the frontmatter block is delimited by `---` lines and contains required keys (at minimum `name` and `description`).
  3. Point `source` at the directory that holds the skill folder(s), not at a deeper file or a parent that lacks SKILL.md.
  4. 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

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


AI-assisted analysis of google-gemini/gemini-cli@5024443c72 (2026-08-12). Data as JSON: /api/errors/7770befe9c0fef75. Report an issue: GitHub.