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

skill catalog {$url} has a symlinked skills/ directory; refu

Error message

skill catalog {$url} has a symlinked skills/ directory; refusing to inspect it

What it means

The catalog's skills/ entry is a symlink, and the installer refuses to inspect it: a catalog-controlled symlink could redirect skill lookup to an arbitrary directory on the host. This trust-boundary check runs before any skill name is resolved or available names are enumerated.

Source

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

        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)]
            ));
        }
        let skills_root = skills_dir.canonicalize().with_context(|| {
            format!(
                "failed to canonicalize catalog skills root {}",
                skills_dir.display()
            )
        })?;
        if !skills_root.starts_with(&clone_root) {
            anyhow::bail!(crate::i18n::get_required_cli_string_with_args(
                "cli-skills-install-catalog-root-escapes",
                &[("url", url)]
            ));
        }
        if !skills_root.is_dir() {
            anyhow::bail!(crate::i18n::get_required_cli_string_with_args(

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Do not use that repository as a catalog; if unexpected, treat it as suspicious
  2. Fork the catalog and replace the symlinked skills entry with a real directory, then install from the fork
Defensive patterns

Strategy: try-catch

Try / catch

match install_git_catalog_skill_source(url, skill, &skills_path, false, &ws) {
    Err(e) if e.to_string().contains("symlinked skills/ directory") => {
        // catalog is untrusted/broken: stop using this URL; do not attempt to
        // bypass — the check protects the host filesystem
    }
    r => r,
}

Prevention

When it happens

Trigger: A catalog repo that commits 'skills' as a symlink (Git supports committing symlinks) — either a deliberately tricky/malicious catalog or an exotic hand-built layout.

Common situations: Security testing catalogs; repos prepared on systems where symlinks were accidentally committed; adversarial-catalog research.

Related errors


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