Running Anthropic's own skill validator over Anthropic's own 25 first-party skills fails 18 of them, almost all for carrying a version key.
Why you'd careThe problem it solves
You finish a skill, find quick_validate.py in the skill-creator plugin, and run it as a sanity check. It fails. You compare your frontmatter against a shipped first-party skill, copy that skill's shape exactly, run it again, and it fails again — on the key you just copied. At this point most people conclude they have misread the docs.
They have not. There are two schemas and they disagree; the Claude Code skills documentation now lays the split out as a table of distribution paths. The standalone validator enforces a closed allowlist of six keys. The Claude Code runtime happily loads skills using keys that allowlist has never heard of, which is why real shipped skills use them. Knowing which of the two you are being judged by is the difference between a five-minute fix and an afternoon.
ConceptWhat it is
Frontmatter is the YAML dictionary between the opening and closing --- fences. Two keys are required by the spec and every upload path: name and description — though inside Claude Code even those are optional. Past that, the schema depends entirely on who is reading.
The validator path. quick_validate.py, shipped inside the skill-creator plugin, enforces a strictly closed allowlist: name, description, license, allowed-tools, metadata, compatibility. Any key outside that set is a hard error naming the offending key. It also enforces per-field rules: name must match ^[a-z0-9-]+$ with no leading, trailing or doubled hyphens and at most 64 characters; description at most 1024 characters with no angle brackets; compatibility, if present, a string of at most 500 characters.
The runtime path. Claude Code loads skills carrying version, tools, argument-hint, disable-model-invocation and user-invocable — none of which are in that allowlist. These are not mistakes; they are keys the harness actually uses. disable-model-invocation: true stops the model auto-selecting a skill. argument-hint feeds slash-command style invocation.
So “unknown keys are rejected” is wrong as a general claim, and this entry previously said so. The accurate version: unknown keys are rejected by every path but one. package_skill.py, claude.ai uploads and the Skills API all fail with a hard error on a key outside the six; only the Claude Code runtime, plugin skills included, accepts them. The runtime is permissive; the packager and the upload paths are not.
How it worksThe mechanics
Measured on 2026-07-25 across the 25 first-party skills in the official plugin marketplace, running the real quick_validate.py against each directory:
Key Uses Validator
name 25/25 required
description 25/25 required
version 13/25 REJECTED
allowed-tools 2/25 allowed
tools 2/25 REJECTED
license 1/25 allowed
argument-hint 1/25 REJECTED
disable-model-invocation 1/25 REJECTED
user-invocable 1/25 REJECTED
metadata 0/25 allowed
compatibility 0/25 allowed
Result: 18 of 25 fail validation.Read the two ends of that table together. The single most common optional key in practice, version, is rejected. The two keys nobody uses at all, metadata and compatibility, are allowed. The allowlist and the corpus have drifted apart almost completely.
Note also tools versus allowed-tools: two spellings for what looks like the same job, two skills using each, and only one of the two spellings accepted. And license is a free-text string, not an SPDX identifier — the one skill that sets it writes license: Complete terms in LICENSE.txt.
This has a consequence that is not cosmetic. package_skill.py calls validate_skill first and aborts on failure with “Please fix the validation errors before packaging.” So those 18 first-party skills cannot be packaged into a .skill archive by Anthropic's own packaging script without editing their frontmatter. If you are distributing through a plugin repository this never comes up, because plugin skills ship as files in the repo. If you are distributing a standalone .skill, or uploading to claude.ai or the Skills API, the validator's schema is the one that binds you, and version is the key that will stop you.
At a glanceSee it
One frontmatter block, two consumers with different schemas, and two very different verdicts.
Where it runsSurfaces and availability
| Surface | Status | Notes |
|---|---|---|
| Claude Code | Yes | Corrected. The documented reference is broad, not merely tolerant: name, description, when_to_use, argument-hint, arguments, disable-model-invocation, user-invocable, allowed-tools, disallowed-tools, model, effort, context, agent, background, hooks, paths, shell, plus the spec's license, compatibility and metadata. Note that name is not required here (it defaults to the directory name and only sets the display label for personal and project skills) and description is "Recommended". No primary page states what happens to keys outside this list. Source: code.claude.com/docs/en/skills, "Frontmatter reference". |
| Agent SDK | Yes | Corrected again: the SDK now honours allowed-tools. "For project and personal skills, Claude Code applies the allowed-tools frontmatter field in SDK sessions" — and the allowedTools query option can pre-approve tools for them as well; skills synced from claude.ai follow their own frontmatter rules. Source: code.claude.com/docs/en/agent-sdk/skills, "Pre-approve tools for skills". |
| quick_validate.py / package_skill.py | Yes | Upgraded back from Unverified: this row names a pair of scripts, not a product surface, but the gate is now documented. code.claude.com/docs/en/skills names packaging with package_skill.py from anthropics/skills in the same row as claude.ai uploads and the Skills API, allows exactly name, description, license, compatibility, metadata and allowed-tools, and says any other field makes packaging or upload "fail with a hard error instead of ignoring the field". None of those pages documents a .skill archive format. |
| Claude Desktop / claude.ai | Yes | Now confirmed. Custom skills are uploaded as zip files through Settings > Features — not a .skill archive — and the upload enforces the same six-field allowlist as the packager: a key outside it fails with a hard error. The documented SKILL.md rules apply too (name and description required, see the API row). Sources: agents-and-tools/agent-skills/overview, "claude.ai"; code.claude.com/docs/en/skills, "Using skill frontmatter outside Claude Code". |
| Claude API / Messages API | Yes | Upgraded: the schema is published, though only in part. Required fields are name and description. name: maximum 64 characters, lowercase letters, numbers and hyphens only, no XML tags, and it cannot contain the reserved words "anthropic" or "claude". description: non-empty, maximum 1,024 characters, no XML tags. Uploads go to /v1/skills as a zip or individual files with SKILL.md at the root of one top-level directory. A key outside the spec's six fields fails the upload with a hard error (code.claude.com/docs/en/skills). Sources: agents-and-tools/agent-skills/overview, "Skill structure"; agent-skills/best-practices, "Technical notes". |
| Managed Agents | Yes | Upgraded. A custom skill is "a directory containing a SKILL.md file plus any supporting files, uploaded to your workspace as a zip archive or as individual files", so the API contract above is what applies, and it applies upstream at registration; the agent then references the result by ID. An optional display_name, up to 255 characters and not unique, can override the name derived from SKILL.md (display_title, unique per workspace, is the beta shape). Source: managed-agents/skills, "Create a custom skill". |
| Amazon Bedrock | No | Confirmed. No skills surface, so no schema. Source: build-with-claude/claude-in-amazon-bedrock, "Features not supported". |
| Google Vertex AI | No | Confirmed, same listing. Source: build-with-claude/claude-on-vertex-ai. |
| Microsoft Foundry | Yes | Was Unverified. Custom Skills upload through the Skills API and Foundry "inherit[s] the same Skills behavior as the Claude API", so the frontmatter contract is the API one. Hosted on Anthropic deployments only. Sources: agents-and-tools/agent-skills/overview; build-with-claude/claude-in-microsoft-foundry. |
| OpenAI Codex CLI / ChatGPT | Yes | Was Unverified, and the "assume nothing transfers" advice needs narrowing: the required-key names do transfer. Codex requires YAML front matter with name and description, plus an optional agents/openai.yaml for UI metadata. What does not transfer is the validation detail — no character limits, reserved words or XML-tag rule are documented. Source: developers.openai.com/codex/skills. For contrast, Antigravity requires only description and makes name optional (antigravity.google/docs/skills). |
With the six-key allowlist now documented, the practical rule is plain. Anything that will leave Claude Code — a claude.ai upload, a Skills API upload, a package_skill.py build — carries only name, description, license, compatibility, metadata and allowed-tools, with name and description written to the published API constraints: 64 characters lowercase-hyphen for the name, 1,024 for the description, no XML tags in either, no "anthropic" or "claude" in the name. Everything else is consumer-specific. Claude Code reads fourteen further keys that every upload path rejects outright; Codex and Antigravity require overlapping but not identical pairs. Add an optional key when a named consumer reads it, and keep it out of any skill you intend to upload.
ExampleIn the real world
You have built an internal plugin with six skills and your team wants two of them shared with a partner org that is not on your marketplace. You decide to send .skill files.
You run python3 quick_validate.py skills/deploy-runbook. It returns: Unexpected key(s) in SKILL.md frontmatter: version. Allowed properties are: allowed-tools, compatibility, description, license, metadata, name. Your reflex is that the validator is out of date, because you copied the frontmatter shape from plugin-dev, which does exactly this.
Both things are true. The key is real and the harness reads past it; the validator does not accept it. You have two options and only one of them is cheap. You can delete the two version: lines — nothing in your setup consumes them — and package successfully. Or you can zip the folders by hand, skipping the gate, and hit the same hard error the moment the partner uploads them to claude.ai or the Skills API.
You delete the lines. Both skills package. The other four keep their version: because they ship in the plugin repo, where the strict path is never invoked. Two schemas, two distribution routes, one repo — and the frontmatter differs by exactly the key that decides which route a skill can take.
Not thisWhat it is often confused with
- Not one schemathere is no single JSON Schema for skill frontmatter that all consumers validate against. The six-key allowlist is the Agent Skills spec's field set, enforced by the packager, claude.ai uploads and the Skills API, and the Claude Code runtime does not share it.
- Not the runtime's schema
quick_validate.pytells you what will package or upload, not what will load in Claude Code. A skill can fail it and work perfectly, as 18 first-party skills do every day. - Not plugin.jsonthat is the plugin manifest, a different file with a different schema, describing the container rather than any skill inside it.
- Not tool permissions
allowed-toolsin frontmatter is not the same mechanism as the settings-level permission system. In Claude Code, frontmatter pre-approves the listed tools for the turn that invokes the skill and restricts nothing; settings still govern every tool it does not list. - Not YAML-validated beyond the fencethe parser only requires a top-level dictionary. Nested structure under an allowed key is not inspected, so a typo inside a nested block fails silently rather than loudly.
LimitsWhen not to reach for it
- You are adding keys speculatively.If no consumer reads it, a key is a future validation failure with no present benefit.
versionis the canonical example. - You are treating validator success as correctness.It checks six key names and three length limits. It says nothing about whether your description triggers or your body is any good.
- You are trying to express behaviour in frontmatter.Frontmatter is metadata. Conditional logic, workflow and sequencing belong in the body, where the model actually reads them.
- You are porting a skill to another vendor.Do not carry the key names across and hope. Read that vendor's current format; the required-key set is not shared.
Verified 2026-09-12. Moves on a scale of months. Re-check before you depend on it. Provider: Anthropic.