Your Skill's Frontmatter Has a Typo. Nothing Will Ever Tell You A developer released frontmatter-guard, a zero-dependency Python CLI that semantically lints Claude Code skill and plugin frontmatter, catching unknown keys and misspelled hook events that the official `claude plugin validate` command silently ignores. The tool flags errors such as an unrecognized `effort` key or a `PreToolUs` hook typo with file:line locations and concrete fix suggestions, and offers CI-ready exit codes plus JSON output. A few days ago I shipped a Claude Code command with this frontmatter: --- name: deploy-helper description: "Helps with deployments" effort: high --- 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 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. effort / licence / descripton catcher. 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. name / description absent or empty. version that isn't semver-ish. hooks: as a string, name: as a number, that sort of thing. 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. 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. 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. pip install frontmatter-guard frontmatter-guard check . --strict GitHub: https://github.com/hahahahahahahahah6/frontmatter-guard https://github.com/hahahahahahahahah6/frontmatter-guard PyPI: https://pypi.org/project/frontmatter-guard/ https://pypi.org/project/frontmatter-guard/ If it catches a typo that would have silently shipped, that was the whole point.