SKILL.md format

A skill is one file. This page is the conformance reference for it: what the format is, who defines it, exactly what SkillMD reads from a SKILL.md, and every rule the linter runs against it before the registry will publish it.

An open standard, not a SkillMD invention

SKILL.md is the file format defined by the Agent Skills specification, an open standard published at agentskills.io/specification. Anthropic developed the format and released it on 18 December 2025, and it is stewarded through the Agentic AI Foundation, a Linux Foundation body. The specification is developed in the open at github.com/agentskills/agentskills.

SkillMD did not write that specification and holds no authority over it. Where you need the normative wording of a requirement, the specification is the source and it outranks this page.

Canonical specification: agentskills.io/specification. Read that first. This page documents what SkillMD checks on top of it.

What SkillMD adds is the conformance layer: a published rule set that turns the format into results you can run yourself. Eight lint rules, plus the parse check that runs before them, a capability scanner, and a quality score. All of it lives in one MIT-licensed package that the CLI, the registry, the MCP server, and the GitHub Action all import, so every number this site shows for a skill can be reproduced against the same file on your own machine.

File structure

A SKILL.md file is YAML frontmatter delimited by --- lines, followed by a Markdown body. The frontmatter block must start at the very first byte of the file. No leading blank line, no BOM, nothing before the opening ---. Both LF and CRLF line endings parse. The whole file must be 256KB or smaller, or it gets rejected outright.

Frontmatter fields

SkillMD's parser reads exactly three fields. Agents and toolchains define frontmatter keys of their own, and anything beyond these three is ignored rather than rejected, so a file carrying extra keys still lints clean here.

FieldRequiredTypeRules
nameRequiredstringNon-empty. Leading and trailing whitespace is trimmed. The parser truncates anything past 120 characters, and an over-long name that reaches the rule engine fails SK002.
descriptionRequiredstringNon-empty. Internal whitespace and newlines collapse to single spaces. Over 1024 characters and the parser rejects the whole file. Under 20 characters triggers a warning, SK010.
licenseOptionalstringAn SPDX identifier is recommended, for example MIT. Silently truncated to 64 characters. Absent triggers a warning, SK011.

The body

Everything after the closing --- is plain Markdown. The agent loads it on demand when it decides to use the skill. It should contain at least one Markdown heading, meaning a line matching ^#{1,6}\s: one to six # characters followed by whitespace, at the start of a line. It should also run past a couple of hundred characters. A body that is just a stub, or that has no heading at all, still passes but gets flagged with a warning.

Minimal example

This is the smallest file that passes validation. It has no license field, so skillmd lint would flag SK011 as a warning, not an error.

---
name: my-skill
description: "One clear sentence describing exactly what this skill does and when to use it."
---

## What this does

This skill helps an agent complete a specific, narrow task. Explain the
task here in plain language, so the agent knows when to reach for it.

## Instructions

Write the actual steps the agent should follow. Be specific about inputs,
outputs, and any edge cases the agent needs to handle along the way.

Fuller example

Same three fields, this time with a license, a longer description, and a body organized into sections.

---
name: pdf-table-extract
description: "Extracts tabular data from PDF documents and returns it as clean CSV or JSON, handling multi-page tables and merged cells."
license: MIT
---

## Overview

This skill extracts tables from PDF files, including tables that span
multiple pages or contain merged header cells. Use it whenever a task
asks for structured data pulled out of a PDF report, invoice, or form.

## Steps

First, identify every page that contains a table. Then extract each
table's rows and columns, preserving column order and header labels.
Merge tables that continue across a page break into a single result.
Normalize whitespace and currency formatting before returning output.

## Error handling

If a page is scanned as an image with no extractable text layer, say so
explicitly instead of returning empty or guessed data. If a table's
structure is ambiguous, such as cells that visually merge but have no
clear delimiter, flag the ambiguity in the output rather than silently
picking one interpretation.

Real-world examples

The registry itself is the best example library. Every skill page shows the full SKILL.md, rendered and raw, and serves a Markdown variant at /skills/<owner>/<name>.md. Start with the official skills from verified publishers, or the most-installed skills to see the patterns that get reused. New to the format? Read What is an Agent Skill? first.

Lint rules

These are the checks SkillMD runs. Errors fail the check and block publishing; warnings do not. Each rule below has a stable anchor, so /docs/format#SK011 links straight to one rule and keeps working as the page grows. Run npx skillmds lint against your file to get the same results locally.

CodeSeverityCondition
SK001ErrorThe file could not be parsed at all.
SK002Errorname must be present and 120 characters or fewer.
SK003Errordescription must be present and 1024 characters or fewer.
SK010Warndescription is shorter than 20 characters.
SK011Warnlicense is not declared.
SK020WarnBody is under 200 characters and looks like a stub.
SK021WarnBody has no Markdown heading.
SK030ErrorBody exceeds 256KB.
SK040WarnThe slug derived from name does not match the skill's directory.

The numbers are deliberately sparse, leaving room to add rules without renumbering the existing ones. The 000s are structural, the 010s are metadata quality, the 020s are body quality, and the 030s and above are limits and layout.

SK001: the file could not be parsed

Severity: error. Message: whatever the parser reported, such as Missing YAML frontmatter (--- ... ---), Invalid YAML frontmatter, frontmatter 'name' is required, or SKILL.md missing or too large (max 256KB).

What it checks. That the file opens with a --- delimiter, that the block between the delimiters is valid YAML, that name and description are both present, that the description is 1024 characters or fewer, and that the file is 256KB or smaller. SK001 is not one of the rules, it is what the linter reports when parsing fails before any rule can run, so a file that raises SK001 gets no other diagnostics at all.

Why it matters. An agent that cannot parse the frontmatter never loads the skill. It does not fall back or guess. This is the difference between a skill that quietly never fires and one that works.

How to fix. Check that the first line of the file is exactly ---, with no BOM, no blank line, and no comment above it. Then check the YAML: a description containing a colon or a leading @ needs quoting, and tabs are not valid YAML indentation.

SK002: name is required and capped at 120 characters

Severity: error. Message: frontmatter 'name' is required or name must be ≤ 120 chars.

What it checks. That name is present after trimming, and that it is no longer than 120 characters. When you lint a raw file, a missing name is caught during parsing and surfaces as SK001. SK002 is the form the same failure takes when the rules are run directly against an already-parsed skill rather than against file text.

Why it matters. The name is the skill's identity. It becomes the slug, the install directory, and the label the agent shows when it decides to load the skill. A name over the limit gets truncated rather than preserved, which silently changes that identity.

How to fix. Give the skill a short, task-shaped name, lowercase with hyphens, for example pdf-table-extract. Keep the detail in description, which has 1024 characters to work with.

SK003: description is required and capped at 1024 characters

Severity: error. Message: frontmatter 'description' is required or description must be ≤ 1024 chars.

What it checks. That description is present after whitespace collapsing, and that it is 1024 characters or fewer.

Why it matters. The description, with the name, is the part of a skill the agent keeps in context. Everything else loads on demand. It is the string the model matches a request against, which makes it the field that decides whether the skill ever runs. An over-long description does not get trimmed either, it rejects the entire file.

How to fix. Write one or two sentences that name the task and the trigger: what the skill does, and the situation in which the agent should reach for it. Include the words a user would actually type.

SK010: description is too terse

Severity: warn. Message: description is very terse (< 20 chars); add more detail to help discovery.

What it checks. That a non-empty description is at least 20 characters after trimming. An empty description is SK003's job, not this rule's.

Why it matters. A description like Formats code. gives a model nothing to match against, so the agent loads the wrong skill or none at all. The same string drives search in the registry, so a terse description also makes the skill unfindable by the people who would install it.

How to fix. Say what it does and when to use it in the same sentence. Twenty characters is a floor, not a target: a description that gets matched reliably usually runs a sentence or two, and names the trigger as well as the task.

SK011: no license declared

Severity: warn. Message: no 'license' declared in frontmatter.

What it checks. That a license key exists in the frontmatter. The value is not validated against the SPDX list.

Why it matters. A skill is source code that an agent takes instructions from. Without a declared license the terms of reuse are unstated, which is enough to block adoption inside most companies. It is also one line to fix, which makes it the cheapest warning on this page to clear.

How to fix. Add one line to the frontmatter: license: MIT, or whichever SPDX identifier matches the repository the skill ships in. skillmd lint --fix can insert it for you.

SK020: body is a stub

Severity: warn. Message: body is very short (< 200 chars); this looks like a stub.

What it checks. That the body is at least 200 characters after trimming. This counts characters, not bytes, and the frontmatter is not included.

Why it matters. A body that short usually means the real instructions live somewhere else, in a README or in someone's head. The agent only ever reads this file, so whatever is missing from it is missing at the moment the skill runs.

How to fix. Write the actual procedure: inputs, steps, the shape of the output, and what to do when the input does not match expectations. Length is not the goal, but a real procedure rarely fits in 200 characters.

SK021: body has no Markdown heading

Severity: warn. Message: body has no Markdown headings; add structure with '#' headings.

What it checks. That at least one line in the body matches ^#{1,6}\s. The space after the hashes is required, so ##Steps does not count and ## Steps does. Setext headings, the underline style, are not detected.

Why it matters. Headings are how a model navigates the file once it is loaded, and how a person skims it before installing. An unstructured wall of prose makes the agent read everything to find the one paragraph that matters.

How to fix. Split the body into named sections, typically an overview, the steps, and the failure cases. Two or three ## headings are usually enough.

SK030: body exceeds 256KB

Severity: error. Message: SKILL.md exceeds 256KB.

What it checks. The UTF-8 byte length of the body against a 256KB ceiling. Bytes, not characters, so non-ASCII content counts for more than its character count suggests.

Why it matters. The whole body enters the agent's context the moment the skill fires. A body this large is both a direct cost on every invocation and a correctness problem, because the instruction that matters gets buried in reference material the model has to read past.

How to fix. Move reference material, schemas, and long examples into companion files next to the SKILL.md and point to them from the body. The agent can open those when it needs them, which is what progressive disclosure is for.

SK040: name does not match its directory

Severity: warn. Message: name slug 'x' does not match directory 'y'.

What it checks. The slug derived from name against the directory or slug the skill lives under. Slugification lowercases, replaces each run of non-alphanumeric characters with a single hyphen, trims leading and trailing hyphens, and cuts the result at 60 characters. This rule only runs when the linter knows the directory, which means during install, publish, or a directory scan. Linting a single loose file never reports it.

Why it matters. The mismatch splits a skill's identity in two: the directory the agent reads from, and the name it displays. Updates then land in one place while the agent looks in the other, and installs collide with unrelated skills that happen to slugify the same way.

How to fix. Rename the directory to match the slugified name, or change name to match the directory. Keep both in lowercase hyphen form and the question never comes up.

Quality score

Every skill carries a quality score from 0 to 100, printed by skillmd lint and stored against the skill in the registry. One function computes it, and it takes two inputs: the diagnostics from the rules above, and the capability flags below.

The shape of it:

  • A file with no diagnostics and no flags sits at the top of the scale. Everything from there is subtraction.
  • Diagnostics are weighted by severity. An error weighs several times a warning, and a warning several times an informational note, so clearing one error is worth more than clearing a handful of nits.
  • Each distinct capability flag takes a further slice, once per flag however many lines matched. The three everyday flags barely move the number. untrusted_install is the outlier and costs by far the most, because fetch-and-execute from a source you do not control is not something a pinned version protects you from.
  • The result is clamped to the 0 to 100 range.

The individual weights are not published here, and they get retuned as the checks improve. They are in the source rather than in a table on this page: the function is qualityScore in @skillmds/core, and it is the same function the registry calls, so you can read the current values instead of taking this description on trust.

Nothing else feeds this number. Not install counts, not ratings, not who published the skill. To raise it, run the linter and fix what it reports.

Capability flags

Alongside the lint rules, SkillMD scans the body line by line and labels what the text reaches for. These are capabilities, not verdicts. A flag says what a skill's instructions can touch, so you can judge the blast radius before installing. It is not a claim that the skill is unsafe.

FlagWhat matches
docs_onlyNothing else matched. The body reads as pure documentation.
executes_scriptsA fenced code block tagged sh, bash, python, py, js, ts, ruby, or rb, or the tokens subprocess, os.system, child_process, exec(.
network_callsfetch, requests., urllib, axios, curl, wget, websocket, or any http:// or https:// URL.
reads_secretsapi_key, api-key, apikey, secret, token, password, os.environ, process.env, getenv.
untrusted_installFetch-and-execute patterns: an install pointed at a custom npm registry or pip index, curl or wget piped into a shell, a PowerShell web request piped into iex, bash <(curl ...), or installing an archive straight from a raw URL.

Matching is case-insensitive and runs per line over the body only. The frontmatter is not scanned. Each finding records the flag, the line number, and the first 120 characters of that line, which is what feeds the SARIF output and the annotations in CI.

The everyday flags fire on ordinary documentation, and that is expected. A skill that is pure documentation trips executes_scripts if it shows a single fenced bash block. The word token anywhere in prose trips reads_secrets, including a sentence explaining that the skill needs no tokens. Any https:// link trips network_calls, including a link to a blog post. This is stated here rather than glossed over, because a flag is a prompt to read the file, not a judgement on it, and the three everyday flags are weighted to match how weak a signal they are.

Read a flag as a question, not an answer. executes_scripts means look at what the script does before you install; untrusted_install means look harder. Treat the set as triage across a large catalog, then read the skill itself, which every skill page on this site shows in full.

Run the checks yourself

Nothing on this page needs to be taken on trust. The parser, the rules, the scanner, and the score are one package, and the CLI, the registry, and the GitHub Action all import it.

Command line. No install step needed:

npx skillmds lint ./my-skill
npx skillmds lint ./my-skill --strict
npx skillmds lint . --format sarif
npx skillmds rules SK010

The npm package is skillmds and the binary it installs is skillmd, so npx skillmds lint and a global skillmd lint run the same code. --strict turns warnings into failures. --format sarif and --format github emit machine-readable output for CI. skillmd rules with no argument lists every rule, and with an id shows one on its own. Full flag reference in the CLI reference.

As a library. The same engine is on npm as @skillmds/core, with parseSkillMd, runRules, scanSecurity, qualityScore, and the lint wrapper around all four:

import { lint } from "@skillmds/core";

const result = lint(source, { slug: "pdf-table-extract" });

result.ok;                 // false when any diagnostic is an error
result.diagnostics;        // [{ id: "SK011", severity: "warn", message: "..." }]
result.security.flags;     // ["executes_scripts", "network_calls"]
result.security.findings;  // every match, with its line number and snippet
result.score;              // 84

In CI. The GitHub Action lints every SKILL.md in a repository and uploads SARIF to the Code scanning tab, so findings land as annotations on the pull request that introduced them. Note the org is skillmds, with an s:

- uses: skillmds/skillmd/action@v1
  with:
    path: skills
    strict: "true"

Source for all three: github.com/skillmds/skillmd and @skillmds/core on npm. If a rule produces a result you think is wrong, or a check you expect is missing, write to hi@skillmd.com.

Gotchas

  • Frontmatter must start at byte 0 of the file. A leading blank line or BOM breaks parsing.
  • Newlines inside description collapse to single spaces, so multi-line descriptions do not render the way they look in your editor.
  • Extra frontmatter keys beyond name, description, and license are ignored, not rejected.
  • An over-long description does not get truncated. It rejects the entire file.
  • A file that fails to parse reports only SK001. Fix that first, then the rest of the diagnostics appear.
  • SK040 stays quiet unless the linter knows the directory, so a clean run on a loose file is not proof the slug matches.
  • Capability flags come from the body alone, and each flag counts once however many lines match it.
  • Always run skillmd lint locally before publishing. It catches everything above before the registry sees it.
  • Use skillmd init <skill-name> to scaffold a valid file instead of writing the frontmatter by hand.