How Claude Code Skill Metadata Fields Are Parsed and Used: A Complete Technical Guide
Claude Code skill metadata fields are extracted from YAML frontmatter in SKILL.md files, parsed into Python dictionaries via BaseChecker._extract_frontmatter, validated against required schemas using checker classes, and consumed by both the validation scripts and the Astro documentation site to enforce constraints and render skill cards.
The Jeffallan/claude-skills repository manages Claude Code capabilities through structured markdown files. Each skill's metadata—including triggers, role definitions, and output formats—lives in a standardized YAML block that powers both automated validation and documentation generation. Understanding how these fields are parsed and used is essential for contributing new skills or extending the validation framework.
Where Skill Metadata Lives
Every skill resides in its own directory under skills/<skill-name>/, containing a mandatory SKILL.md file. This file must begin with a YAML frontmatter block delimited by triple dashes:
---
metadata:
triggers: ["react", "component"]
role: "frontend expert"
scope: "file"
output-format: "typescript"
domain: "web development"
related-skills: ["typescript-basics", "testing-react"]
---
The metadata map is the central data structure that subsequent parsing and validation stages consume.
Extracting and Parsing Metadata
The scripts/validate-skills.py file contains the core extraction logic in the BaseChecker class. The process follows a strict pipeline from raw markdown to structured data.
Locating the SKILL.md File
The validator first resolves the skill path and locates the markdown file:
skill_md = skill_path / "SKILL.md"
This logic resides in BaseChecker._extract_frontmatter at line 44 of validate-skills.py. If the file is missing, the checker immediately flags a validation error.
Splitting Frontmatter from Body
Once loaded, the raw content undergoes delimiter checking and splitting:
if not content.startswith("---"):
# Error: Missing frontmatter delimiter
return None
parts = content.split("---", 2)
# parts[0] = empty prefix
# parts[1] = YAML content
# parts[2] = Markdown body
This three-way split preserves the YAML metadata separately from the instructional content that follows.
YAML Parsing Implementation
The extracted YAML string passes through a compatibility layer that prefers PyYAML but falls back to a custom parser:
def parse_yaml(yaml_str: str) -> dict:
if HAS_PYYAML:
return yaml.safe_load(yaml_str) or {}
return simple_yaml_parse(yaml_str)
This function, defined around line 24 in validate-skills.py, ensures the metadata block converts to a Python dictionary regardless of environment dependencies.
The final extraction wraps everything in a dataclass:
return FrontmatterResult(frontmatter, parts[2], skill_md)
Where frontmatter contains the parsed metadata dictionary ready for validation.
Validating Metadata Fields
After parsing, specialized checker classes validate the metadata dictionary against the schema defined in CLAUDE.md. Each checker inherits from BaseChecker and targets specific constraints.
Required Field Validation
The MetadataFieldsChecker ensures the metadata map contains all mandatory fields:
REQUIRED_METADATA_FIELDS = [
"triggers",
"role",
"scope",
"output-format",
"domain",
"related-skills"
]
def check(self, result: FrontmatterResult) -> List[ValidationIssue]:
metadata = result.frontmatter.get("metadata", {})
for field in REQUIRED_METADATA_FIELDS:
if field not in metadata:
yield ValidationIssue(
file=result.path,
message=f"Missing required metadata field: {field}"
)
This validation occurs in MetadataFieldsChecker.check and prevents incomplete skill definitions from passing CI.
Enum Constraint Enforcement
Two specialized checkers validate that certain fields contain only allowed values:
ScopeEnumChecker: Validatesmetadata.scopeagainstVALID_SCOPESOutputFormatEnumChecker: Validatesmetadata.output-formatagainstVALID_OUTPUT_FORMATS
Both inherit from MetadataEnumChecker, which reads the allowed values from constants defined around line 150 in validate-skills.py. If a skill declares scope: invalid-value, the checker emits a validation error before the documentation site builds.
Cross-Skill Reference Checking
The CrossRefChecker uses the related-skills field to build a dependency graph:
def _check_orphans(self, all_skills: Dict[str, FrontmatterResult]):
for skill_name, result in all_skills.items():
related = result.frontmatter.get("metadata", {}).get("related-skills", [])
for ref in related:
if ref not in all_skills:
yield ValidationIssue(
file=result.path,
message=f"Related skill '{ref}' does not exist"
)
This ensures that metadata.related-skills only references valid, existing skills within the repository.
Consuming Metadata in the Documentation Site
While Python scripts handle validation, the Astro documentation site consumes the same metadata at build time. The configuration in site/src/content.config.ts defines the skills collection:
import { defineCollection, z } from 'astro:content';
const skills = defineCollection({
schema: z.object({
metadata: z.object({
triggers: z.array(z.string()),
role: z.string(),
scope: z.enum(['file', 'project', 'global']),
'output-format': z.string(),
domain: z.string(),
'related-skills': z.array(z.string())
}).optional()
})
});
Astro automatically parses the YAML frontmatter when loading skills/*/SKILL.md files. The TypeScript definitions in .astro/content.d.ts expose this metadata to components:
export interface RenderedContent {
html: string;
metadata?: {
imagePaths: Array<string>;
[key: string]: unknown;
};
}
This enables the site to render skill cards showing the domain badge, filter search results by scope or output-format, and generate navigation links based on related-skills.
Practical Implementation Examples
Running the Validator for a Single Skill
Validate a specific skill from the command line:
python -m scripts.validate-skills --skill react-expert
This executes BaseChecker._extract_frontmatter to load skills/react-expert/SKILL.md, then runs MetadataFieldsChecker, ScopeEnumChecker, and CrossRefChecker to ensure compliance with the schema defined in CLAUDE.md.
Programmatically Reading Skill Metadata
Access parsed metadata in Python scripts:
from pathlib import Path
from scripts.validate_skills import BaseChecker
skill_path = Path("skills/react-expert")
result = BaseChecker._extract_frontmatter(skill_path)
if result:
metadata = result.frontmatter.get("metadata", {})
print("Domain:", metadata.get("domain"))
print("Triggers:", metadata.get("triggers"))
print("Related Skills:", metadata.get("related-skills"))
The BaseChecker._extract_frontmatter method is static and reusable, returning a FrontmatterResult dataclass containing the parsed dictionary, markdown body, and file path.
Accessing Metadata in Astro Components
Consume metadata in the documentation frontend:
---
import { getEntry } from 'astro:content';
const skill = await getEntry({ collection: 'skills', slug: 'react-expert' });
const { metadata } = skill?.data ?? {};
---
<h2>{skill?.title}</h2>
{metadata?.domain && (
<span class="badge">{metadata.domain}</span>
)}
{metadata?.triggers && (
<ul>
{metadata.triggers.split(", ").map(trigger => (
<li>{trigger}</li>
))}
</ul>
)}
{metadata?.['related-skills'] && (
<nav>
<h3>Related Skills</h3>
{metadata['related-skills'].map(related => (
<a href={`/skills/${related}`}>{related}</a>
))}
</nav>
)}
This TypeScript/Astro template renders the same metadata dictionary that the Python validator processes, ensuring consistency between validation and presentation layers.
Summary
- Skill metadata resides in YAML frontmatter within
skills/<skill-name>/SKILL.mdfiles, nested under a top-levelmetadatakey containing fields liketriggers,role,scope,output-format,domain, andrelated-skills. - Extraction uses
BaseChecker._extract_frontmatterinscripts/validate-skills.py, which splits the markdown on---delimiters and parses the YAML content into a Python dictionary usingparse_yaml(preferring PyYAML with a fallback to a custom parser). - Validation enforces schema compliance through specialized checkers:
MetadataFieldsCheckerensures required fields exist,ScopeEnumCheckerandOutputFormatEnumCheckervalidate against allowed values, andCrossRefCheckerverifies thatrelated-skillsreferences point to existing skills. - Documentation consumption happens via Astro, which reads the same YAML frontmatter through
site/src/content.config.tsand exposes the metadata to TypeScript components for rendering skill cards, search filters, and navigation links.
Frequently Asked Questions
What happens if a SKILL.md file is missing the metadata block?
The MetadataFieldsChecker emits a ValidationIssue error indicating that the metadata key is missing or incomplete. If the file lacks the --- delimiter entirely, BaseChecker._extract_frontmatter returns None, causing the validation pipeline to flag the file as having no parseable frontmatter.
Can I use custom fields in the metadata section?
While the MetadataFieldsChecker enforces a specific set of REQUIRED_METADATA_FIELDS, the YAML parser itself preserves all key-value pairs in the frontmatter dictionary. The Astro documentation site receives the complete metadata object, so additional fields can be accessed in TypeScript components, though they won't be validated by the Python checkers unless explicitly added to the schema in CLAUDE.md.
How does the validator handle YAML syntax errors?
The parse_yaml function in scripts/validate-skills.py attempts to use yaml.safe_load from PyYAML first. If PyYAML is unavailable, it falls back to simple_yaml_parse, a minimal custom parser. Syntax errors in the YAML block cause the parser to raise an exception, which the validation pipeline catches and reports as a ValidationIssue pointing to the specific file and error description.
What is the relationship between metadata validation and the Astro documentation site?
The Python validation scripts and the Astro site operate independently but consume the same source data. The Python checkers ensure that metadata fields conform to the schema before content is committed, while Astro's content collection (defined in site/src/content.config.ts) reads the valid YAML at build time to generate static pages. This separation ensures that only schema-compliant skills appear in the documentation, with the Python layer acting as a gatekeeper for the frontend.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →