sipeed/picoclaw · error
skill frontmatter is required
Error message
skill frontmatter is required
What it means
validateAppliedSkillBody checks that an applied skill body (after TrimSpace) begins with the exact 4-byte prefix "---\n" - the opening YAML frontmatter delimiter. The evolution apply pipeline refuses to write a skill file that lacks frontmatter, since name and description metadata are mandatory downstream. A body starting with anything else - prose, a heading, or even '---' followed by CRLF - fails this check.
Source
Thrown at pkg/evolution/apply.go:149
}
skillDir := filepath.Dir(skillPath)
if err := os.Remove(skillDir); err != nil && !os.IsNotExist(err) && !isDirNotEmptyError(err) {
return err
}
return nil
}
func isDirNotEmptyError(err error) bool {
if err == nil {
return false
}
return strings.Contains(strings.ToLower(err.Error()), "directory not empty")
}
func validateAppliedSkillBody(body, targetSkillName string, allowExtraFrontmatterFields bool) error {
body = strings.TrimSpace(body)
if !strings.HasPrefix(body, "---\n") {
return fmt.Errorf("skill frontmatter is required")
}
if !strings.Contains(body, "\n# ") {
return fmt.Errorf("skill heading is required")
}
frontmatter, _ := splitSkillFrontmatter(body)
fields, err := parseSkillFrontmatterFields(frontmatter, allowExtraFrontmatterFields)
if err != nil {
return err
}
name := strings.TrimSpace(fields["name"])
if name == "" {
return fmt.Errorf("skill frontmatter name is required")
}
if name != targetSkillName {
return fmt.Errorf("skill frontmatter name %q does not match target skill %q", name, targetSkillName)
}
if strings.TrimSpace(fields["description"]) == "" {
return fmt.Errorf("skill frontmatter description is required")View on GitHub (pinned to 49183d7e8d)
Solutions
- Prepend a minimal frontmatter block: '---\nname: <skill>\ndescription: <what it does>\n---\n'
- Normalize line endings to LF before applying (dos2unix, or strings.ReplaceAll(body, "\\r\\n", "\\n"))
- Strip any BOM/leading spaces so the file's first bytes are exactly '---' followed by a newline
- Run the same validation locally before submitting the change: check strings.HasPrefix(strings.TrimSpace(body), "---\\n")
Example fix
# before: body starts with a heading # My Skill Does things. # after: frontmatter block first --- name: my-skill description: Does things. --- # My Skill Does things.
Defensive patterns
Strategy: validation
Validate before calling
func hasFrontmatter(body string) bool {
return strings.HasPrefix(strings.TrimSpace(
strings.ReplaceAll(body, "\r\n", "\n")), "---\n")
} Try / catch
if err := apply.Change(body); err != nil {
if strings.Contains(err.Error(), "skill frontmatter is required") {
return fmt.Errorf("skill file must start with a --- frontmatter block: %w", err)
}
return err
} Prevention
- Always begin skill files with the --- delimiter line containing name and description
- Normalize CRLF to LF in generated skill content before applying
- Strip BOMs when reading skill sources on Windows toolchains
- Validate with a lint step in CI (prefix check) before evolution changes are applied
When it happens
Trigger: Applying a ChangeKind replace/create whose skill markdown has no '---' block at the top; a body authored on Windows whose first line ends with \r\n (so the prefix is "---\r\n" and does not match "---\n"); frontmatter separated from the body but with a leading BOM or spaces before the first ---.
Common situations: LLM-generated or hand-edited skill content that begins directly with '# Title'; files saved with CRLF line endings; content pasted from a renderer that strips the frontmatter block; BOM added by Windows editors.
Related errors
- skill heading is required
- skill frontmatter name is required
- skill frontmatter name %q does not match target skill %q
- skill frontmatter description is required
- skill patch frontmatter name %q does not match target skill
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/7d8d1976eadc279a.
Report an issue: GitHub.