{"record":{"id":"877a2b906f802e2e","repo":"deepset-ai/haystack","slug":"skill-frontmatter-is-opened-with-but-never-c","errorCode":null,"errorMessage":"Skill frontmatter is opened with '---' but never closed with a matching '---' line.","messagePattern":"Skill frontmatter is opened with '---' but never closed with a matching '---' line\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"haystack/skill_stores/file_system/skill_store.py","lineNumber":44,"sourceCode":"    Split a `SKILL.md` file into its YAML frontmatter and markdown body.\n\n    The frontmatter is the YAML block delimited by a leading and a trailing line containing exactly `---`.\n    If the first line is not `---`, no frontmatter is present and an empty mapping and the original text\n    are returned.\n\n    :param text: The full contents of a `SKILL.md` file.\n    :returns: A tuple of (frontmatter mapping, body).\n    :raises ValueError: If the frontmatter is opened with `---` but never closed, is not valid YAML, or is\n        not a YAML mapping.\n    \"\"\"\n    lines = text.lstrip().split(\"\\n\")\n    if lines[0].rstrip() != \"---\":\n        return {}, text\n\n    # Find the closing delimiter: the next line containing exactly '---'.\n    closing_index = next((i for i, line in enumerate(lines[1:], start=1) if line.rstrip() == \"---\"), None)\n    if closing_index is None:\n        raise ValueError(\"Skill frontmatter is opened with '---' but never closed with a matching '---' line.\")\n\n    frontmatter_block = \"\\n\".join(lines[1:closing_index])\n    body = \"\\n\".join(lines[closing_index + 1 :])\n    try:\n        loaded = yaml.safe_load(frontmatter_block) or {}\n    except yaml.YAMLError as e:\n        raise ValueError(f\"Skill frontmatter is not valid YAML: {e}\") from e\n    if not isinstance(loaded, dict):\n        raise ValueError(\"Skill frontmatter must be a YAML mapping.\")  # noqa: TRY004\n    return loaded, body.lstrip(\"\\n\")\n\n\nclass FileSystemSkillStore:\n    \"\"\"\n    SkillStore backed by a directory of skill sub-directories on the local filesystem.\n\n    Expected layout:\n","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/skill_stores/file_system/skill_store.py#L26-L62","documentation":"Skill files use YAML frontmatter delimited by two `---` lines. _parse_frontmatter finds the opening `---` on the first line but no closing line containing exactly `---`, so it raises ValueError because the frontmatter block is unterminated and the skill metadata cannot be parsed.","triggerScenarios":"Loading a skill whose file starts with `---` but lacks a matching closing `---` line — e.g. truncated file, the closing delimiter accidentally indented or written as `----` or `-- -`, or content merged that swallowed the delimiter.","commonSituations":"Hand-authored skill files with missing final `---`; editors or copy-paste stripping trailing delimiter lines; template generation bugs that emit the opening fence but not the closing one.","solutions":["Add a closing line containing exactly `---` after the frontmatter key/value block","Ensure the closing delimiter is on its own line with no leading/trailing spaces (line.rstrip() == \"---\")","Validate the whole frontmatter block parses as YAML (the next check after this one)"],"exampleFix":"// before\n---\nname: my-skill\ndescription: Does things\n# file ends without closing delimiter\n// after\n---\nname: my-skill\ndescription: Does things\n---","handlingStrategy":"validation","validationCode":"lines = text.splitlines()\nif lines and lines[0].rstrip() == \"---\":\n    assert any(l.rstrip() == \"---\" for l in lines[1:]), \"frontmatter never closed with '---'\"","typeGuard":"def has_closed_frontmatter(text: str) -> bool:\n    lines = text.splitlines()\n    if not lines or lines[0].rstrip() != \"---\":\n        return True  # no frontmatter, not an error\n    return any(l.rstrip() == \"---\" for l in lines[1:])","tryCatchPattern":"try:\n    meta, body = store.load_skill(name)\nexcept ValueError as e:\n    if \"never closed\" in str(e):\n        text = text.rstrip() + \"\\n---\\n\"  # repair missing closing delimiter\n        meta, body = parse_frontmatter(text)\n    else:\n        raise","preventionTips":["Always end frontmatter with a line containing exactly `---`","Lint skill files for balanced frontmatter delimiters in CI","Avoid editors/templates that strip trailing delimiter lines"],"tags":["python","yaml","frontmatter","parsing","skills"],"backgroundTag":"unterminated-yaml-frontmatter","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}