Anatomy of a SKILL.md file

A field guide to the SKILL.md format: the frontmatter fields that get parsed, how lint rules and quality scores work, and the mistakes that quietly tank a score.

Contents

A SKILL.md file is small on purpose: YAML frontmatter followed by a Markdown body, almost nothing else. An agent loads it, reads a couple of fields, and decides whether to pull in the rest. But small formats still have rules. Get the frontmatter wrong and the file fails to parse. Get the body wrong and nobody trusts the skill enough to install it.

This post walks through the format field by field, explains how skillmd lint checks it, and shows how the quality score gets computed.

What a SKILL.md actually is

A SKILL.md is two things stacked on top of each other:

  1. YAML frontmatter, delimited by --- on its own line at the top and again after the fields.
  2. A Markdown body that an agent loads on demand.

Two constraints trip people up immediately. The frontmatter must start at byte zero: no leading blank line, no BOM, nothing before the first ---. If your editor injects a UTF-8 byte order mark on save, the parser sees garbage before the delimiter and the file fails. Second, the entire file, frontmatter plus body, must stay under 256KB, generous for documentation but not infinite.

The three frontmatter fields that matter

Here is the part people don’t expect: you can put whatever extra keys you want in the frontmatter, and the parser ignores them. Only three fields are actually read.

name

Required. A string, 120 characters or fewer. Go over that and lint fails outright, not just a warning. Keep it short, and match it to the install directory, since the two are meant to line up.

description

Required. This is the field an agent reads to decide whether the skill is relevant before it opens the body, so it carries real weight.

  • Internal newlines collapse to spaces. Write it across multiple lines in your editor for readability; it flattens at parse time.
  • Over 1024 characters and the entire file is rejected, not trimmed.
  • Under 20 characters and lint just warns. It still works, but a nine-word description tells an agent almost nothing.

license

Optional. Use an SPDX identifier, like MIT or Apache-2.0. It’s truncated to 64 characters if you go long. Leaving it off just warns.

That’s the entire frontmatter contract. Anything else you add, tags, version numbers, author fields, passes through untouched. The linter doesn’t check it and the runtime doesn’t read it.

The body

The body is plain Markdown, and it’s what the agent actually loads and acts on once it decides the skill is relevant. There’s no strict schema, but two soft expectations shape how it’s scored:

  • At least one Markdown heading (#, ##, whatever level makes sense).
  • More than a stub, roughly 200 characters or more.

Neither fails your build; both warn. Treat the body as the actual instructions: steps, code, context, the stuff the description was advertising.

Lint rules

Running skillmd lint checks the file against numbered rules. Errors fail the check; warnings don’t. A few you’ll actually run into:

  • SK001 frontmatter is missing or malformed, or the file exceeds the size cap. Error.
  • SK002 name is missing or over 120 characters. Error.
  • SK003 description is missing or over 1024 characters. Error.
  • SK010 description is under 20 characters. Warning.
  • SK011 license is missing. Warning.
  • SK020 body is under roughly 200 characters. Warning.
  • SK021 body has no heading. Warning.

Run it locally before you publish:

npm i -g skillmds
skillmd init my-skill
skillmd lint my-skill/SKILL.md

skillmd init scaffolds a starting file. skillmd lint is what actually validates it.

The quality score

Every skill gets a quality score from 0 to 100, shown alongside it. It starts at 100 and loses points from two sources.

Lint results: each error costs 30 points, each warning costs 10. Two warnings and an error puts you at 40.

A textual security scan of the body: a pattern scan across the text, not an execution sandbox. It subtracts points for signal patterns it finds: reading secrets costs 25, executing scripts costs 15, network calls cost 8.

Because the scan is textual, it’s blunt. A skill that documents a shell command, or mentions an API key in an instructional sentence, can trip the same flag as a skill that actually reads credentials. The scanner can’t tell explaining a pattern from doing it. If your score comes back lower than expected, check whether your prose uses words like “secret,” “token,” or “curl” in ways that read as literal instructions rather than documentation. You can usually reword around it without losing the point.

A worked example

A minimal but valid SKILL.md, missing only the optional license:

---
name: pdf-extract
description: Extracts text and tables from PDF files using pdfplumber, with page-range and layout options.
---

## Usage

Run this skill when a user needs structured text or table data pulled out of a PDF instead of just raw text.

## Steps

1. Load the PDF with pdfplumber.
2. Extract text page by page, or target a page range if the user specifies one.
3. For tables, use `page.extract_tables()` and return structured rows rather than flattened text.

This passes lint clean except for the SK011 warning on the missing license. It has a heading, content past the stub threshold, and a description specific enough to be useful.

A fuller version adds the license and a longer description:

---
name: pdf-extract
description: Extracts text, tables, and layout metadata from PDF files using pdfplumber, with support for page ranges, rotated pages, and table structure detection.
license: MIT
---

Nothing else changes structurally, but the longer description and the license field push the score higher and remove both warnings.

Common mistakes

A leading blank line or BOM before the first ---. The most common way a file silently fails to parse. If lint says the frontmatter is invalid but it looks fine to you, check for a byte order mark from an editor that defaults to UTF-8-with-BOM.

A description that’s technically valid but useless. Nineteen characters won’t trigger SK002, but “does PDF stuff” doesn’t tell an agent anything it can act on. Write it as if it’s the only sentence anyone will ever read about the skill, because for relevance matching, it is.

A body that’s all preamble, no heading. Reads fine to a person skimming it, but it trips SK021 and gives the agent nothing to anchor on. Add at least one ## section.

Security-flag surprises from ordinary documentation. If a skill legitimately shells out to a script or calls a network endpoint, expect the scan to flag it. But describing those same operations in prose, without executing anything, can trip the same pattern. Reword instructional text if a warning shows up somewhere you didn’t expect.

Confusing the registry’s safety review with lint. Lint is deterministic and you can run it yourself before publishing. The registry’s separate safety review is a different, later step that skillmd lint doesn’t reproduce locally. Passing lint clean means your file is well-formed, not that you can skip that review.

Run skillmd lint before every publish. It’s fast, exact about what it checks, and it catches frontmatter mistakes that are otherwise invisible until something downstream fails to parse your skill at all.