{"record":{"id":"7d8d1976eadc279a","repo":"sipeed/picoclaw","slug":"skill-frontmatter-is-required","errorCode":null,"errorMessage":"skill frontmatter is required","messagePattern":"skill frontmatter is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/evolution/apply.go","lineNumber":149,"sourceCode":"\t}\n\tskillDir := filepath.Dir(skillPath)\n\tif err := os.Remove(skillDir); err != nil && !os.IsNotExist(err) && !isDirNotEmptyError(err) {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc isDirNotEmptyError(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\treturn strings.Contains(strings.ToLower(err.Error()), \"directory not empty\")\n}\n\nfunc validateAppliedSkillBody(body, targetSkillName string, allowExtraFrontmatterFields bool) error {\n\tbody = strings.TrimSpace(body)\n\tif !strings.HasPrefix(body, \"---\\n\") {\n\t\treturn fmt.Errorf(\"skill frontmatter is required\")\n\t}\n\tif !strings.Contains(body, \"\\n# \") {\n\t\treturn fmt.Errorf(\"skill heading is required\")\n\t}\n\tfrontmatter, _ := splitSkillFrontmatter(body)\n\tfields, err := parseSkillFrontmatterFields(frontmatter, allowExtraFrontmatterFields)\n\tif err != nil {\n\t\treturn err\n\t}\n\tname := strings.TrimSpace(fields[\"name\"])\n\tif name == \"\" {\n\t\treturn fmt.Errorf(\"skill frontmatter name is required\")\n\t}\n\tif name != targetSkillName {\n\t\treturn fmt.Errorf(\"skill frontmatter name %q does not match target skill %q\", name, targetSkillName)\n\t}\n\tif strings.TrimSpace(fields[\"description\"]) == \"\" {\n\t\treturn fmt.Errorf(\"skill frontmatter description is required\")","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/evolution/apply.go#L131-L167","documentation":"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.","triggerScenarios":"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 ---.","commonSituations":"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.","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\")"],"exampleFix":"# before: body starts with a heading\n# My Skill\nDoes things.\n\n# after: frontmatter block first\n---\nname: my-skill\ndescription: Does things.\n---\n# My Skill\nDoes things.","handlingStrategy":"validation","validationCode":"func hasFrontmatter(body string) bool {\n    return strings.HasPrefix(strings.TrimSpace(\n        strings.ReplaceAll(body, \"\\r\\n\", \"\\n\")), \"---\\n\")\n}","typeGuard":null,"tryCatchPattern":"if err := apply.Change(body); err != nil {\n    if strings.Contains(err.Error(), \"skill frontmatter is required\") {\n        return fmt.Errorf(\"skill file must start with a --- frontmatter block: %w\", err)\n    }\n    return err\n}","preventionTips":["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"],"tags":["evolution","skills","validation","frontmatter","markdown"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}