DEV Community

hao li
hao li

Posted on Originally published at github.com

Your Skill's Frontmatter Has a Typo. Nothing Will Ever Tell You

Your skill's frontmatter has a typo. Nothing will ever tell you.

A few days ago I shipped a Claude Code command with this frontmatter:

---
name: deploy-helper
description: "Helps with deployments"
effort: high
---
Enter fullscreen mode Exit fullscreen mode

effort: high was supposed to keep the model from going overboard. I ran
claude plugin validate before shipping. It passed. Green. Ship it.

Except effort is not a Claude Code frontmatter key. It's a Codex concept. My
setting silently fell back to the session default, and the official validator
— the thing whose entire job is to validate — had nothing to say about it.

This is the failure mode that bothers me most: not the loud crash, but the
silent no-op. A misspelled PreToolUs hook never fires. A licence key never
licenses anything. You read your own config, it looks right, and it does
nothing. Forever.

So I built frontmatter-guard: a semantic linter for skill/plugin frontmatter
that knows the real key vocabulary and the real hook event list, and fails
loudly when you stray from it.

pip install frontmatter-guard
frontmatter-guard check .claude/ --strict
Enter fullscreen mode Exit fullscreen mode
commands/deploy.md:4: error [unknown-key] Unknown key 'effort'.
    fix: Remove the key -- unknown keys are silently ignored, so it currently does nothing.
commands/deploy.md:7: error [unknown-hook-event] Unknown hook event 'PreToolUs'. Did you mean 'PreToolUse'?
    fix: Rename 'PreToolUs' to 'PreToolUse'. Unknown hook events never fire.
Enter fullscreen mode Exit fullscreen mode

What it checks

  • unknown-key (error) — top-level key not in the known skill/plugin vocabulary, with a difflib "did you mean" suggestion. This is the effort/licence/descripton catcher.
  • unknown-hook-event (error) — event names validated against Claude Code's actual event list (PreToolUse, PostToolUse, SessionStart, SessionEnd, UserPromptSubmit, Stop, SubagentStop, Notification, PreCompact). A wrong event name means your hook never runs, which is exactly the kind of thing you want CI to scream about.
  • missing-required (error) — name/description absent or empty.
  • bad-version (warning) — version that isn't semver-ish.
  • bad-type (warning) — hooks: as a string, name: as a number, that sort of thing.
  • parse-warning (warning) — frontmatter YAML beyond the supported subset. Warned, never crashed.

Every finding carries file:line, the rule name, and a concrete fix suggestion. Exit codes are CI-ready: 1 on any error (or any warning under --strict), 0 when clean, 2 on usage errors. --format json for machines.

Why not just use the official validator?

Because they answer different questions. claude plugin validate asks "will this plugin load?" — compatibility and structure. frontmatter-guard asks "does everything you wrote actually do something?" — semantic strictness. Unknown keys sail through the official check silently; they fail here. They're complementary, not competing.

There's also damson/skill-lint, a CI action doing structural checks on skills. frontmatter-guard is a local stdlib-only CLI doing semantic checks — same command works in pre-commit for instant feedback and in CI for enforcement.

The engineering bit

Zero dependencies, Python 3.9+. The YAML parsing is a hand-rolled subset parser in the standard library — block maps and sequences, inline flow collections, literal blocks, quoted scalars. That sounds risky, but the design decision is deliberate: frontmatter is a small, boring corner of YAML, and a dependency-free parser means the tool installs in one second and runs anywhere, including locked-down CI runners. Anything outside the subset produces a parse-warning and the lint continues — a linter that crashes on weird input is worse than useless.

It scans both morphologies: *.md frontmatter behind --- fences (skills, commands) and plugin.json files, with the same rule set applied to both.

Try it

pip install frontmatter-guard
frontmatter-guard check . --strict
Enter fullscreen mode Exit fullscreen mode

GitHub: https://github.com/hahahahahahahahah6/frontmatter-guard
PyPI: https://pypi.org/project/frontmatter-guard/

If it catches a typo that would have silently shipped, that was the whole point.

Top comments (0)