{"record":{"id":"c81c75e3afa34156","repo":"zylon-ai/private-gpt","slug":"missing-frontmatter","errorCode":"MISSING_FRONTMATTER","errorMessage":"SKILL.md must start with YAML frontmatter","messagePattern":"SKILL\\.md must start with YAML frontmatter","errorType":"exception","errorClass":"SkillDomainError","httpStatus":null,"severity":"error","filePath":"private_gpt/components/skills/parser.py","lineNumber":78,"sourceCode":"    @field_validator(\"allowed_tools_raw\", mode=\"before\")\n    @classmethod\n    def normalize_list_or_str(cls, value: str | list[str] | None) -> str | None:\n        if value is None:\n            return None\n        if isinstance(value, list):\n            return \" \".join(str(item).strip() for item in value if str(item).strip())\n        return value\n\n\nclass ParsedSkillDocument(BaseModel):\n    frontmatter: SkillFrontmatter\n    body: str = Field(default=\"\")\n\n\ndef parse_skill_markdown(skill_markdown: str) -> ParsedSkillDocument:\n    match = _FRONTMATTER_RE.match(skill_markdown)\n    if not match:\n        raise SkillDomainError(\n            SkillErrorCode.MISSING_FRONTMATTER,\n            \"SKILL.md must start with YAML frontmatter\",\n        )\n\n    raw_frontmatter = match.group(1)\n    try:\n        parsed_yaml = yaml.safe_load(raw_frontmatter)\n    except yaml.YAMLError as e:\n        raise SkillDomainError(\n            SkillErrorCode.INVALID_FRONTMATTER,\n            \"The SKILL.md frontmatter is not valid YAML.\",\n        ) from e\n    if not isinstance(parsed_yaml, dict):\n        raise SkillDomainError(\n            SkillErrorCode.INVALID_FRONTMATTER,\n            \"Invalid SKILL.md frontmatter\",\n        )\n","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/skills/parser.py#L60-L96","documentation":"parse_skill_markdown expects the SKILL.md content to begin with a YAML frontmatter block (delimited --- fences) matched by _FRONTMATTER_RE; when the regex does not match at position 0 it raises SkillDomainError with code MISSING_FRONTMATTER. This means the parser found no opening fence/structure at the very start of the document — frontmatter must literally be the first thing in the file.","triggerScenarios":"Calling parse_skill_markdown on a string that does not start with '---\\n' frontmatter — e.g. a README-style markdown file, a SKILL.md beginning with prose or a BOM, or a file whose fences use '···' or are indented.","commonSituations":"Authors writing a title/intro line before the frontmatter; UTF-8 BOM prepended by Windows editors; using alternate fence styles or leaving a blank first line; passing the wrong file's content into the parser.","solutions":["Make '---' the absolute first line, followed by YAML, closed by a second '---', then the body.","Strip any leading whitespace/blank lines or BOM before parsing (`content.lstrip('\\ufeff').lstrip()`).","Ensure you pass the SKILL.md content, not another markdown file.","Add an authoring-time lint that checks the file starts with '---'."],"exampleFix":"# before\n# My Skill\n---\nname: my-skill\n---\nBody...\n\n# after\n---\nname: my-skill\n---\n# My Skill\nBody...","handlingStrategy":"validation","validationCode":"def has_frontmatter(content: str) -> bool:\n    return bool(_FRONTMATTER_RE.match(content)) or content.startswith(\"---\\n\")\n\ndef normalize(content: str) -> str:\n    return content.lstrip(\"\\ufeff\").lstrip()","typeGuard":null,"tryCatchPattern":"try:\n    parsed = parse_skill_markdown(content)\nexcept SkillDomainError as e:\n    if e.code is SkillErrorCode.MISSING_FRONTMATTER:\n        raise ValueError(\"SKILL.md must begin with a '---' YAML block; got bare markdown\") from e\n    raise","preventionTips":["Make '---' the literal first line of every authored SKILL.md.","Strip BOM and leading blank lines before parsing.","Use a skill template/scaffold tool so structure is never hand-mistaken."],"tags":["skills","frontmatter","parsing","markdown","yaml"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}