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

skill '{$skill}' not found in {$url}: no skills/ directory,

Error message

skill '{$skill}' not found in {$url}: no skills/ directory, or it is empty

What it means

After cloning the catalog repo into a temp dir, symlink_metadata on <clone>/skills returned NotFound: the catalog has no skills/ directory at its root. The catalog layout contract is <repo>/skills/<skill-name>/, so a repo without a root skills/ directory cannot serve as a skills catalog.

Source

Thrown at crates/zeroclaw-runtime/src/skills/mod.rs:2398

        )
    })?;

    (|| {
        // Establish the catalog trust boundary before looking up a selected
        // name or enumerating available names. A catalog controls `skills/`,
        // so following it before this check could inspect an arbitrary host
        // directory even when the requested skill does not exist.
        let clone_root = clone_dir.canonicalize().with_context(|| {
            format!(
                "failed to canonicalize catalog clone {}",
                clone_dir.display()
            )
        })?;
        let skills_dir = clone_dir.join("skills");
        let skills_meta = match std::fs::symlink_metadata(&skills_dir) {
            Ok(metadata) => metadata,
            Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
                anyhow::bail!(crate::i18n::get_required_cli_string_with_args(
                    "cli-skills-install-skill-not-in-catalog-empty",
                    &[("skill", skill_name), ("url", url)]
                ));
            }
            Err(err) => {
                return Err(err).with_context(|| {
                    format!(
                        "failed to read metadata for catalog skills root {}",
                        skills_dir.display()
                    )
                });
            }
        };
        if skills_meta.file_type().is_symlink() {
            anyhow::bail!(crate::i18n::get_required_cli_string_with_args(
                "cli-skills-install-catalog-root-symlink",
                &[("url", url)]
            ));

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Check the repo: it must contain a skills/ directory at the root with one directory per skill
  2. If the skills live elsewhere, clone the repo yourself and install the wanted folder via the local-source installer
  3. Use a catalog repo that follows the <repo>/skills/<name>/ layout
Defensive patterns

Strategy: try-catch

Validate before calling

// Optional pre-flight: confirm the repo looks like a catalog before offering it
let out = std::process::Command::new("git")
    .args(["ls-remote", url])
    .output()?;
if !out.status.success() {
    anyhow::bail!("catalog repo unreachable: {url}");
}
// layout itself can only be verified after clone — handle at runtime

Try / catch

match install_git_catalog_skill_source(url, skill, &skills_path, false, &ws) {
    Err(e) if e.to_string().contains("no skills/ directory, or it is empty")
            && !e.to_string().contains("Available skills") => {
        // catalog lacks the <repo>/skills/ layout: use a proper catalog repo,
        // or clone manually and install the folder via the local installer
    }
    r => r,
}

Prevention

When it happens

Trigger: URL points at a repo that stores skills at the repository root or under a different folder (skill/, packages/); the URL points at an ordinary project repo rather than a skills catalog.

Common situations: Pointing the catalog install at a normal project repository; a catalog repo that changed its layout between versions.

Related errors


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