{"record":{"id":"5b20204f6d52e7d2","repo":"charmbracelet/crush","slug":"no-yaml-frontmatter-found","errorCode":null,"errorMessage":"no YAML frontmatter found","messagePattern":"no YAML frontmatter found","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/skills/skills.go","lineNumber":196,"sourceCode":"\tskill.Instructions = strings.TrimSpace(body)\n\n\treturn &skill, nil\n}\n\n// splitFrontmatter extracts YAML frontmatter and body from markdown content.\nfunc splitFrontmatter(content string) (frontmatter, body string, err error) {\n\t// Strip UTF-8 BOM for compatibility with editors that include it.\n\tcontent = strings.TrimPrefix(content, \"\\uFEFF\")\n\t// Normalize line endings to \\n for consistent parsing.\n\tcontent = strings.ReplaceAll(content, \"\\r\\n\", \"\\n\")\n\tcontent = strings.ReplaceAll(content, \"\\r\", \"\\n\")\n\n\tlines := strings.Split(content, \"\\n\")\n\tstart := slices.IndexFunc(lines, func(line string) bool {\n\t\treturn strings.TrimSpace(line) != \"\"\n\t})\n\tif start == -1 || strings.TrimSpace(lines[start]) != \"---\" {\n\t\treturn \"\", \"\", errors.New(\"no YAML frontmatter found\")\n\t}\n\n\tendOffset := slices.IndexFunc(lines[start+1:], func(line string) bool {\n\t\treturn strings.TrimSpace(line) == \"---\"\n\t})\n\tif endOffset == -1 {\n\t\treturn \"\", \"\", errors.New(\"unclosed frontmatter\")\n\t}\n\tend := start + 1 + endOffset\n\n\tfrontmatter = strings.Join(lines[start+1:end], \"\\n\")\n\tbody = strings.Join(lines[end+1:], \"\\n\")\n\treturn frontmatter, body, nil\n}\n\n// Discover finds all valid skills in the given paths.\nfunc Discover(paths []string) []*Skill {\n\tskills, _ := DiscoverWithStates(paths)","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/skills/skills.go#L178-L214","documentation":"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.","triggerScenarios":"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.","commonSituations":"Plain markdown files placed in a skills directory; files saved with a UTF-8 BOM; accidentally deleting the opening `---` while editing.","solutions":["Start the SKILL.md with a line containing exactly `---`","Remove any BOM or leading blank/whitespace-prefixed characters so `---` is the first non-blank line","Ensure the delimiter line has no trailing spaces or extra dashes"],"exampleFix":"// before (SKILL.md)\n# My skill\n---\nname: my-skill\n---\n// after\n---\nname: my-skill\ndescription: Does things\n---\n# My skill","handlingStrategy":"validation","validationCode":"func hasFrontmatter(content string) bool {\n\tfor _, line := range strings.Split(content, \"\\n\") {\n\t\tif strings.TrimSpace(line) == \"\" { continue }\n\t\treturn strings.TrimSpace(line) == \"---\"\n\t}\n\treturn false\n}","typeGuard":null,"tryCatchPattern":"fm, body, err := skills.ParseContent(content)\nif err != nil {\n\treturn fmt.Errorf(\"malformed skill file (frontmatter): %w\", err)\n}","preventionTips":["Start every SKILL.md with a `---` line on line 1","Save files without BOM","Never put prose above the frontmatter block"],"tags":["skills","frontmatter","parsing"],"backgroundTag":"missing-frontmatter","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}