deepset-ai/haystack · error · ValueError

Skill frontmatter is opened with '---' but never closed with

Error message

Skill frontmatter is opened with '---' but never closed with a matching '---' line.

What it means

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.

Source

Thrown at haystack/skill_stores/file_system/skill_store.py:44

    Split a `SKILL.md` file into its YAML frontmatter and markdown body.

    The frontmatter is the YAML block delimited by a leading and a trailing line containing exactly `---`.
    If the first line is not `---`, no frontmatter is present and an empty mapping and the original text
    are returned.

    :param text: The full contents of a `SKILL.md` file.
    :returns: A tuple of (frontmatter mapping, body).
    :raises ValueError: If the frontmatter is opened with `---` but never closed, is not valid YAML, or is
        not a YAML mapping.
    """
    lines = text.lstrip().split("\n")
    if lines[0].rstrip() != "---":
        return {}, text

    # Find the closing delimiter: the next line containing exactly '---'.
    closing_index = next((i for i, line in enumerate(lines[1:], start=1) if line.rstrip() == "---"), None)
    if closing_index is None:
        raise ValueError("Skill frontmatter is opened with '---' but never closed with a matching '---' line.")

    frontmatter_block = "\n".join(lines[1:closing_index])
    body = "\n".join(lines[closing_index + 1 :])
    try:
        loaded = yaml.safe_load(frontmatter_block) or {}
    except yaml.YAMLError as e:
        raise ValueError(f"Skill frontmatter is not valid YAML: {e}") from e
    if not isinstance(loaded, dict):
        raise ValueError("Skill frontmatter must be a YAML mapping.")  # noqa: TRY004
    return loaded, body.lstrip("\n")


class FileSystemSkillStore:
    """
    SkillStore backed by a directory of skill sub-directories on the local filesystem.

    Expected layout:

View on GitHub (pinned to e318778c9b)

Solutions

  1. Add a closing line containing exactly `---` after the frontmatter key/value block
  2. Ensure the closing delimiter is on its own line with no leading/trailing spaces (line.rstrip() == "---")
  3. Validate the whole frontmatter block parses as YAML (the next check after this one)

Example fix

// before
---
name: my-skill
description: Does things
# file ends without closing delimiter
// after
---
name: my-skill
description: Does things
---
Defensive patterns

Strategy: validation

Validate before calling

lines = text.splitlines()
if lines and lines[0].rstrip() == "---":
    assert any(l.rstrip() == "---" for l in lines[1:]), "frontmatter never closed with '---'"

Type guard

def has_closed_frontmatter(text: str) -> bool:
    lines = text.splitlines()
    if not lines or lines[0].rstrip() != "---":
        return True  # no frontmatter, not an error
    return any(l.rstrip() == "---" for l in lines[1:])

Try / catch

try:
    meta, body = store.load_skill(name)
except ValueError as e:
    if "never closed" in str(e):
        text = text.rstrip() + "\n---\n"  # repair missing closing delimiter
        meta, body = parse_frontmatter(text)
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/877a2b906f802e2e. Report an issue: GitHub.