Debugging skills: lint, scan, and the quality score
How to diagnose a failing or low-scoring skill locally with skillmd lint, rules, and scan before you publish, instead of guessing.
Contents
A skill that fails validation on the registry gives you a result, not a reason. Locally you have three commands that give you both: skillmd lint tells you what’s wrong and why, skillmd rules explains any code lint throws at you, and skillmd scan checks the body for security-relevant patterns before something else catches them. Run all three before you publish and you fix problems in your editor instead of in a support thread.
npm i -g skillmds
Start with lint
Point skillmd lint at a SKILL.md and it validates the frontmatter, checks the body, and prints a quality score out of 100.
skillmd lint my-skill/SKILL.md
Plain text output is fine for reading in a terminal, but you’ll want other formats depending on where this runs.
skillmd lint my-skill/SKILL.md --format json
skillmd lint my-skill/SKILL.md --format sarif
skillmd lint my-skill/SKILL.md --format github
json gives you a structured result you can pipe into another script. sarif is the format most code-scanning dashboards expect, so if your CI already ingests SARIF from other linters, this slots in the same way. github emits workflow-annotation syntax, so failures show up inline on the diff in a pull request instead of buried in a log.
Two flags change how strict the run is:
skillmd lint my-skill/SKILL.md --strict
skillmd lint my-skill/SKILL.md --errors-only
--strict treats warnings as failures, which is what you want once a skill is past first draft and you’re trying to keep it clean rather than just parseable. --errors-only does the opposite: it filters the output down to blocking problems, useful when a skill has a pile of style warnings you’re not ready to deal with yet and you just want to know if it’s broken.
If a diagnostic has an obvious, non-destructive fix, --fix applies it directly:
skillmd lint my-skill/SKILL.md --fix
This won’t rewrite your prose or invent a description for you. It handles the mechanical stuff, the kind of fix where there’s exactly one reasonable answer.
Reading a diagnostic with rules
Lint output identifies problems by code: SK002, SK021, and so on. Rather than guessing what a code means from the one-line message, ask the CLI directly.
skillmd rules
That lists every rule lint can raise, with its severity. To read the detail on a specific one:
skillmd rules SK010
The codes you’ll run into most:
- SK001 frontmatter missing or malformed, or the file is over the size limit. Error.
- SK002
namemissing or over 120 characters. Error. - SK003
descriptionmissing or over 1024 characters. Error. - SK010
descriptionunder roughly 20 characters. Warning. - SK011
licensemissing. Warning. - SK020 body under roughly 200 characters. Warning.
- SK021 body has no heading. Warning.
- SK030 body over 256KB. Error.
- SK040
namedoesn’t match the directory the skill lives in. Warning.
Errors block a clean lint run. Warnings don’t, but they’re the first thing worth fixing since they’re also what the quality score penalizes hardest per point of effort. SK010 and SK021 in particular are usually a two-minute fix: write a real description, add one ## heading to the body.
Running the security scan
Lint checks structure. skillmd scan checks content, specifically whether the body contains patterns associated with reading secrets, executing scripts, or making network calls.
skillmd scan my-skill/SKILL.md
It’s worth being clear about what this actually is: a textual scan, pattern matching over the body text. It isn’t a sandbox and it doesn’t run anything. That cuts both ways. It catches skills that genuinely shell out or read environment variables, but it also flags a skill that merely writes “run curl against the API” in a paragraph explaining what the skill does. The scanner can’t distinguish a documented example from an executed one, so if you see a flag you didn’t expect, check whether your prose is describing an operation in words that read as literal instructions.
To fail a build on a specific flag rather than just observing it, use --deny:
skillmd scan my-skill/SKILL.md --deny executes_scripts network_access
This is the version you want in CI: pass the flags you actually consider disqualifying for your use case, and the command exits non-zero if the scan finds them. A skill that’s meant to be pure documentation, for instance, has no business tripping executes_scripts, so denying it there catches a mistake early. A skill whose entire point is to call an API is going to trip network_access no matter what you do, so you’d leave that one off the deny list and just let the score reflect it.
Reading and improving the score
Every skill gets a score from 0 to 100. It starts at 100 and two things subtract from it.
Lint diagnostics: each error costs 30 points, each warning costs 10. A skill with one error and two warnings is already at 50.
Scan penalties: reading secrets costs 25, executing scripts costs 15, network calls cost 8. These stack with lint penalties, not replace them, so a skill that trips both a lint warning and a scan flag pays for both.
The fastest way to raise a score is almost never to rewrite the skill. It’s to fix the two or three warnings lint already told you about:
skillmd lint my-skill/SKILL.md --format json | grep -c "warning"
Add a heading if SK021 is firing. Extend the description past 20 characters if SK010 is firing. Add a license identifier if SK011 is firing. Each of those is a ten-point warning cleared in under a minute, and together they usually close most of the gap between a mediocre score and a clean one.
If a scan penalty is dragging the score down and the flag is a false positive from documentation, the fix is usually rewording rather than removing content: say what the command does without writing it as an instruction the scanner reads literally. Rerun skillmd scan after the edit to confirm the flag actually clears rather than assuming it did.
A local pre-publish loop
Put the three commands together and you get a repeatable check before anything goes out:
skillmd lint my-skill/SKILL.md --strict
skillmd scan my-skill/SKILL.md --deny reads_secrets executes_scripts
skillmd lint my-skill/SKILL.md --format json > lint-result.json
Run this locally, or wire the same three lines into CI using --format github on the lint step so failures annotate the pull request directly. Either way, you’re catching frontmatter errors, thin descriptions, and unintended security flags before a reviewer or a script does it for you.