charmbracelet/crush · error

no YAML frontmatter found

Error message

no YAML frontmatter found

What it means

splitFrontmatter splits a SKILL.md into YAML frontmatter and body. It throws this error when the first non-blank line of the content is not exactly `---`, meaning there is no YAML frontmatter block to parse.

Source

Thrown at internal/skills/skills.go:196

	skill.Instructions = strings.TrimSpace(body)

	return &skill, nil
}

// splitFrontmatter extracts YAML frontmatter and body from markdown content.
func splitFrontmatter(content string) (frontmatter, body string, err error) {
	// 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)

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Start the SKILL.md with a line containing exactly `---`
  2. Remove any BOM or leading blank/whitespace-prefixed characters so `---` is the first non-blank line
  3. Ensure the delimiter line has no trailing spaces or extra dashes

Example fix

// before (SKILL.md)
# My skill
---
name: my-skill
---
// after
---
name: my-skill
description: Does things
---
# My skill
Defensive patterns

Strategy: validation

Validate before calling

func hasFrontmatter(content string) bool {
	for _, line := range strings.Split(content, "\n") {
		if strings.TrimSpace(line) == "" { continue }
		return strings.TrimSpace(line) == "---"
	}
	return false
}

Try / catch

fm, body, err := skills.ParseContent(content)
if err != nil {
	return fmt.Errorf("malformed skill file (frontmatter): %w", err)
}

Prevention

When it happens

Trigger: Calling ParseContent on a file that starts with prose/markdown instead of a `---` delimited block; a BOM or stray characters before the opening `---`; frontmatter delimiters written as `----` or with trailing content on the line.

Common situations: Plain markdown files placed in a skills directory; files saved with a UTF-8 BOM; accidentally deleting the opening `---` while editing.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/5b20204f6d52e7d2. Report an issue: GitHub.