charmbracelet/crush · error
unclosed frontmatter
Error message
unclosed frontmatter
What it means
splitFrontmatter throws this error when it finds an opening `---` line but no closing `---` line anywhere after it, so the frontmatter block is unterminated and the YAML cannot be extracted.
Source
Thrown at internal/skills/skills.go:203
// Strip UTF-8 BOM for compatibility with editors that include it.
content = strings.TrimPrefix(content, "\uFEFF")
// Normalize line endings to \n for consistent parsing.
content = strings.ReplaceAll(content, "\r\n", "\n")
content = strings.ReplaceAll(content, "\r", "\n")
lines := strings.Split(content, "\n")
start := slices.IndexFunc(lines, func(line string) bool {
return strings.TrimSpace(line) != ""
})
if start == -1 || strings.TrimSpace(lines[start]) != "---" {
return "", "", errors.New("no YAML frontmatter found")
}
endOffset := slices.IndexFunc(lines[start+1:], func(line string) bool {
return strings.TrimSpace(line) == "---"
})
if endOffset == -1 {
return "", "", errors.New("unclosed frontmatter")
}
end := start + 1 + endOffset
frontmatter = strings.Join(lines[start+1:end], "\n")
body = strings.Join(lines[end+1:], "\n")
return frontmatter, body, nil
}
// Discover finds all valid skills in the given paths.
func Discover(paths []string) []*Skill {
skills, _ := DiscoverWithStates(paths)
return skills
}
// DiscoverWithStates finds all valid skills in the given paths and also
// returns a per-file state slice describing parse/validation outcomes. Useful
// for diagnostics and UI reporting.
func DiscoverWithStates(paths []string) ([]*Skill, []*SkillState) {View on GitHub (pinned to 7944b8e522)
Solutions
- Add the closing `---` line after the YAML keys
- Ensure the closing delimiter is on its own line with no indentation or extra characters
- Validate the file renders as frontmatter + body by re-running the parser after editing
Example fix
// before --- name: my-skill description: Does things // after --- name: my-skill description: Does things ---
Defensive patterns
Strategy: validation
Validate before calling
func frontmatterClosed(content string) bool {
lines := strings.Split(content, "\n")
count := 0
for _, l := range lines {
if strings.TrimSpace(l) == "---" { count++ }
}
return count >= 2
} Try / catch
fm, body, err := skills.ParseContent(content)
if err != nil {
return fmt.Errorf("unterminated frontmatter: %w", err)
} Prevention
- Always close the frontmatter block with a second `---`
- Check file endings after scripted generation
- Preview parsed skill files before committing
When it happens
Trigger: A SKILL.md with `---` on the first non-blank line but no second `---` delimiter; the closing delimiter accidentally deleted during editing; closing delimiter indented or written with extra characters so it no longer trims to `---`.
Common situations: Manual edits that removed the closing fence; heredoc/script generation that wrote only one delimiter; copy-paste truncating the file's end.
Related errors
- no YAML frontmatter found
- parsing frontmatter: %w
- name is required
- description is required
- name exceeds %d characters
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/46ae329770a80f9f.
Report an issue: GitHub.