Skip to content
On this page

The validate-skills action ​

.github/actions/validate-skills is this repository's validator for repositories that host agent skills. Vivswan/repo-platform's own ci.yml runs it on repo-platform's skills directory; this repository runs it on itself in checks.yml.

The layout it validates:

text
skills/             # the skills directory (input skills-dir, default `skills`)
  README.md         # index of the hosted skills; required as soon as the directory exists
  my-skill/
    SKILL.md        # YAML frontmatter: name (= folder, kebab-case), description
    ...             # whatever else the skill carries
.claude-plugin/
  plugin.json       # the published catalog (input plugin-manifest); its `skills` array lists `./skills/my-skill`
  marketplace.json  # optional; checked for consistency with plugin.json when present

A skill folder is unpublished until plugin.json lists it. Installers and discovery mode read the manifest, not the disk, so an unlisted folder validates green and never ships.

Inputs ​

InputMeaningDefault
skills-dirDirectory holding the skills, relative to the repo rootskills
plugin-manifestPath of the plugin manifest listing the published skills.claude-plugin/plugin.json
modestructure or discoverystructure

Both paths must be plain relative paths inside the checkout: no absolute paths, no . or .. segments.

What each mode checks ​

An empty catalog ("skills": []) passes both modes: a freshly adopted repository publishes nothing yet.

ModeNetworkWhat a red means
structureofflinethe catalog structure is broken; cheap, so it belongs in the merge gate
discoveryneeds npmthe real npx -y skills@<pinned> add . --list does not list every skill in plugin.json

Structure mode, in the order it reports:

  • plugin.json parses and has a kebab-case name.
  • Each path in its skills array is a real direct child of the skills directory with a SKILL.md.
  • The skills directory is a real directory or absent. Anything else at the path is an error.
  • The skills directory carries an index README.md at its root.
  • Every folder under the skills directory, listed or not, has a SKILL.md with frontmatter.
  • Its name equals the folder and is kebab-case, 64 characters max.
  • Its description is nonempty, 1024 characters max.
  • Its frontmatter does not set metadata.internal to true: the skills CLI silently drops such a skill at install time, so the value on a published skill hides it from consumers while every gate stays green. Any other value publishes.
  • A .mcp.json in a skill folder, when present, parses as JSON.
  • marketplace.json, when present next to the manifest, has a kebab-case name and a non-empty plugins list.
  • Each marketplace plugin's source path stays inside the repository; a source object naming another repository (git-subdir, github, url) is left to claude plugin validate, since nothing of it is in the checkout.
  • A marketplace plugin's skills (one path or a list) resolve under its source and must stay inside it, each a kebab-case folder with a SKILL.md; the root plugin (source: "./") keeps the repository's skills directory rule.
  • A plugin publishing the repository root carries the same name as plugin.json.
  • Symlinks are rejected anywhere on a validated path, ancestors included. A link can point outside the checkout, so what ships would not be what was validated.
  • The one exception: a marketplace plugin's source may pass through in-repo links while its physical path stays inside the repository.

Discovery mode downloads the CLI from the npm registry at the exact version validate-skills.ts records in SKILLS_CLI_VERSION (a CLI release cannot red a green catalog; the pin moves by hand), makes up to three attempts, and matches each published skill name on word boundaries in the listing. Give it its own job; a caller decides whether discovery gates its merges (repo-platform gates it).

Exit codes:

ExitMeaning
0clean
1one error: line per finding, then a count
2unknown mode

The full rule set is the source, validate-skills.ts. Its tests are tests/repo/validate-skills.test.ts.

How repo-platform calls it ​

repo-platform's ci.yml pins the action to a commit on main (the comment records which main it took) and passes its skills directory:

yaml
- uses: Vivswan/skills/.github/actions/validate-skills@<sha> # main, <date>
  with:
    skills-dir: skills
    mode: structure

The action sets up bun through the fleet's shared bun-setup action, which walks up from the action's folder to this repository's own root .bun-version, the pin the fleet sync keeps current. The bun pin never comes from the caller's checkout. The caller needs no bun step and no install step.

How this repository runs it on itself ​

  • CI: the validate-skills-action job in .github/workflows/checks.yml runs the composite action on this checkout in structure mode, inside the all-green gate. That job exercises the action as repo-platform consumes it, action.yml and the root bun pin included.
  • Locally and in the pre-commit hook, bun run validate runs the same script with the defaults (bun run check includes it):
sh
SKILLS_DIR=skills PLUGIN_MANIFEST=.claude-plugin/plugin.json MODE=structure bun .github/actions/validate-skills/validate-skills.ts
  • Discovery, the same command with MODE=discovery (needs network).
  • bun run test covers the action's unit tests along with the rest of the suite.

The action is the baseline any repository hosting skills can call. Everything specific to this catalog (per-skill README and codex manifest, template/, the marketplace shape with its upstream entries, cross-file drift) is bun run smoke, scripts/smoke.mts.

Built from main at 0066e1dSource: docs/validate-skills.md