zeroclaw-labs/zeroclaw · error · anyhow::Error

Skill content is missing YAML front-matter (expected `---` d

Error message

Skill content is missing YAML front-matter (expected `---` delimited block at top)

What it means

validate_skill_content splits SKILL.md on a --- delimited YAML front-matter block via split_front_matter, which requires the file to start with a literal '---\n' (after CRLF normalization) and to contain a closing '---' on its own line. This error means no such block was found, so there is nowhere to read the required name field from.

Source

Thrown at crates/zeroclaw-runtime/src/skills/improver.rs:140

        self.cooldowns.insert(slug.to_string(), Instant::now());

        Ok(Some(slug.to_string()))
    }

    fn skills_dir(&self) -> PathBuf {
        self.workspace_dir.join("skills")
    }
}

/// Validate skill content: must be non-empty, have a parseable YAML front-matter
/// block with a non-empty `name` field.
pub fn validate_skill_content(content: &str) -> Result<()> {
    if content.trim().is_empty() {
        bail!("Skill content is empty");
    }

    let Some((front, _body)) = split_front_matter(content) else {
        bail!("Skill content is missing YAML front-matter (expected `---` delimited block at top)");
    };

    let name = front_matter_value(&front, "name").unwrap_or_default();
    if name.trim().is_empty() {
        bail!("Skill front-matter missing required `name` field");
    }

    Ok(())
}

/// Split a SKILL.md into (front_matter_text, body_text).
/// Returns `None` if the file doesn't start with `---\n` or has no closing
/// `---` delimiter.
fn split_front_matter(content: &str) -> Option<(String, String)> {
    let normalized = content.replace("\r\n", "\n");
    let rest = normalized.strip_prefix("---\n")?;
    if let Some(idx) = rest.find("\n---\n") {
        Some((rest[..idx].to_string(), rest[idx + 5..].to_string()))

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Prepend a block that starts the file: ---\nname: <slug>\n---\n followed by the body
  2. Remove any leading blank lines, comments, or BOM bytes before the opening ---
  3. Make sure the closing --- sits on its own line with a newline after the last front-matter key
  4. Pre-validate with the public validate_skill_content before calling improve_skill

Example fix

# before (body only)
Here is how to deploy...

# after
---
name: deploy-guide
---
Here is how to deploy...
Defensive patterns

Strategy: validation

Validate before calling

// Reuse the library's own public validator before the write path:
zeroclaw_runtime::skills::validate_skill_content(&improved_content)?;
improver.improve_skill(slug, improved_content, reason).await?;

Type guard

fn has_yaml_front_matter(content: &str) -> bool {
    let n = content.replace("\r\n", "\n");
    match n.strip_prefix("---\n") {
        Some(rest) => rest.contains("\n---\n") || rest.ends_with("\n---"),
        None => false,
    }
}

Try / catch

match improve_skill(...).await {
    Err(e) if e.to_string().contains("missing YAML front-matter") => {
        // regenerate with the original front-matter preserved, then retry once
    }
    r => r,
}

Prevention

When it happens

Trigger: Passing markdown body with no front-matter; front-matter not at byte 0 (leading blank line, comment, or UTF-8 BOM); missing closing '---'; an opening '---' immediately followed by text on the same line instead of a newline.

Common situations: LLM-generated improvements that rewrite the file and drop the front-matter; hand-edited SKILL.md files; content assembled by concatenation that puts prose before the front-matter block; editors saving with a BOM.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/379e4a00b9c555f5. Report an issue: GitHub.