{"record":{"id":"7770befe9c0fef75","repo":"google-gemini/gemini-cli","slug":"no-valid-skills-found-in-sourcepath-ensure-a","errorCode":null,"errorMessage":"No valid skills found in \"${sourcePath}\". Ensure a SKILL.md file exists with valid frontmatter.","messagePattern":"No valid skills found in \"(.+?)\"\\. Ensure a SKILL\\.md file exists with valid frontmatter\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/utils/skillUtils.ts","lineNumber":226,"sourceCode":"/**\n * Central logic for linking a skill from a local path via symlink.\n */\nexport async function linkSkill(\n  source: string,\n  scope: 'user' | 'workspace',\n  onLog: (msg: string) => void,\n  requestConsent: (\n    skills: SkillDefinition[],\n    targetDir: string,\n  ) => Promise<boolean> = () => Promise.resolve(true),\n): Promise<Array<{ name: string; location: string }>> {\n  const sourcePath = path.resolve(source);\n\n  onLog(`Searching for skills in ${sourcePath}...`);\n  const skills = await loadSkillsFromDir(sourcePath);\n\n  if (skills.length === 0) {\n    throw new Error(\n      `No valid skills found in \"${sourcePath}\". Ensure a SKILL.md file exists with valid frontmatter.`,\n    );\n  }\n\n  // Check for internal name collisions\n  const seenNames = new Map<string, string>();\n  for (const skill of skills) {\n    if (seenNames.has(skill.name)) {\n      throw new Error(\n        `Duplicate skill name \"${skill.name}\" found at multiple locations:\\n  - ${seenNames.get(skill.name)}\\n  - ${skill.location}`,\n      );\n    }\n    seenNames.set(skill.name, skill.location);\n  }\n\n  const workspaceDir = process.cwd();\n  const storage = new Storage(workspaceDir);\n  const targetDir =","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/5024443c7217464a66e98f80d73172a26440bd8f/packages/cli/src/utils/skillUtils.ts#L208-L244","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait linkSkills({ source: './my-tool' }); // my-tool has no SKILL.md\n\n// after\nawait linkSkills({ source: './my-tool/skills/my-skill' }); // folder contains SKILL.md with frontmatter","handlingStrategy":"validation","validationCode":"import { pathExists, readFileSync } from 'fs-extra/esm';\nimport yaml from 'js-yaml';\n\nasync function assertValidSkillDir(sourcePath: string) {\n  const skillFile = path.join(sourcePath, 'SKILL.md');\n  if (!(await pathExists(skillFile))) {\n    throw new Error(`No SKILL.md at ${skillFile}`);\n  }\n  const text = readFileSync(skillFile, 'utf8');\n  const m = text.match(/^---\\n([\\s\\S]*?)\\n---/);\n  if (!m) throw new Error('SKILL.md is missing frontmatter delimiters');\n  const fm = yaml.load(m[1]) as Record<string, unknown>;\n  if (!fm.name || !fm.description) {\n    throw new Error('SKILL.md frontmatter needs name and description');\n  }\n  return fm;\n}\n\n// before linkSkills:\nawait assertValidSkillDir(path.resolve(source));","typeGuard":"function hasValidSkillFrontmatter(text: string): boolean {\n  const m = text.match(/^---\\n([\\s\\S]*?)\\n---/);\n  if (!m) return false;\n  try {\n    const fm = JSON.parse(\n      // crude yaml-as-json check; use a real parser in production\n      m[1]\n        .replace(/^(\\w+):/gm, '\"$1\":')\n        .replace(/'/g, '\"'),\n    );\n    return typeof fm.name === 'string' && typeof fm.description === 'string';\n  } catch {\n    return false;\n  }\n}","tryCatchPattern":"try {\n  await linkSkills(source, scope, onLog, consent);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('No valid skills found')) {\n    onLog(`Skipping ${source}: ${e.message}`);\n    return [];\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["skills","filesystem","config","validation"],"backgroundTag":null,"analyzedSha":"5024443c7217464a66e98f80d73172a26440bd8f","analyzedAt":"2026-08-12T06:01:53.711Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}