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

Skill content is empty

Error message

Skill content is empty

What it means

Thrown by validate_skill_content (crates/zeroclaw-runtime/src/skills/improver.rs) when the skill content handed to SkillImprover::improve_skill is empty or whitespace-only. The improver refuses to write blank content because an atomic rename of empty text would erase the existing SKILL.md. It fires on the incoming improved_content argument, and again on the temp file after write as a self-check.

Source

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

                    md_path.display()
                )
            })?;

        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)> {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Check improved_content.trim().is_empty() before calling improve_skill; skip or re-request the generation when empty
  2. If content comes from an LLM, retry the generation and require a non-empty completion before improving
  3. Log the slug and the raw provider response so empty generations are visible in telemetry
  4. If the message is prefixed 'Validation failed after write', the temp-file self-check failed: inspect what wrote .SKILL.md.tmp in your workspace skills dir

Example fix

// before
improver.improve_skill(slug, improved_content, reason).await?; // fails on empty LLM output

// after
if improved_content.trim().is_empty() {
    tracing::warn!(slug, "empty improvement content; skipping");
    return Ok(None);
}
improver.improve_skill(slug, improved_content, reason).await?;
Defensive patterns

Strategy: validation

Validate before calling

// Before calling improve_skill:
if improved_content.trim().is_empty() {
    tracing::warn!(slug, "empty improvement content; skipping");
    return Ok(None);
}
improver.improve_skill(slug, improved_content, reason).await?;

Type guard

fn has_skill_body(content: &str) -> bool {
    !content.trim().is_empty()
}

Prevention

When it happens

Trigger: Calling improve_skill(slug, "", reason) or with content containing only whitespace/newlines. Passing through an LLM completion that came back empty (truncation, content filter, mis-parsed streaming chunk) without checking it.

Common situations: Self-improvement pipelines where the provider returns an empty message; an upstream bug that reads the wrong file into improved_content; a generation step that returns Ok(String::new()) on soft failure.

Related errors


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