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

Skill path escapes skills directory: {name}

Error message

Skill path escapes skills directory: {name}

What it means

When removing a skill that lives in the global skills dir, the code canonicalizes both the skill directory and the global root and requires the skill path to remain under the root before remove_dir_all runs. If the skill path resolves outside the canonical root — typically via a symlink — the command refuses to delete, a containment guard against destroying arbitrary directories. A missing/unresolvable global root also triggers this, because the root falls back to its uncanonicalized path while the child canonicalizes successfully, so the starts_with check fails.

Source

Thrown at src/skills/mod.rs:444

                        service
                            .remove_skill(&target, zeroclaw_runtime::skills::RemoveMode::Archive)
                            .map_err(anyhow::Error::msg)?;
                        println!(
                            "{}",
                            get_required_cli_string_with_args(
                                "cli-skills-removed-archived",
                                &[("status", &status), ("name", &name), ("bundle", alias)],
                            )
                        );
                    } else {
                        // Global dir: plain delete with a containment guard.
                        let global_root = skills_dir(&config.data_dir);
                        let canonical_root =
                            global_root.canonicalize().unwrap_or(global_root.clone());
                        if let Ok(c) = dir.canonicalize()
                            && !c.starts_with(&canonical_root)
                        {
                            anyhow::bail!("Skill path escapes skills directory: {name}");
                        }
                        std::fs::remove_dir_all(dir)?;
                        println!(
                            "{}",
                            get_required_cli_string_with_args(
                                "cli-skills-removed-global",
                                &[("status", &status), ("name", &name)],
                            )
                        );
                    }
                }
                many => {
                    let locs = many
                        .iter()
                        .map(|(l, _)| l.clone())
                        .collect::<Vec<_>>()
                        .join(", ");
                    anyhow::bail!(

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Inspect the entry: `ls -la <data-dir>/skills/<name>` and look for a '->' symlink target
  2. Replace the symlink with the real directory (or delete it outright) and re-run the remove
  3. Verify the global skills root exists as a real directory (skills_dir of your configured data_dir)
  4. If data_dir was relocated, fix it in config so root and children canonicalize consistently

Example fix

# before: <data-dir>/skills/foo is a symlink → elsewhere
zeroclaw skills remove foo            # refuses: escapes skills directory

# after: make it a real directory (or rm the link), then remove
rm <data-dir>/skills/foo
git clone https://github.com/owner/foo <data-dir>/skills/foo
zeroclaw skills remove foo
Defensive patterns

Strategy: validation

Validate before calling

// Mirror the containment guard before delegating the removal
let root = skills_dir(&config.data_dir).canonicalize()?;
let dir = root.join(&name);
let c = dir.canonicalize().map_err(|_| "skill dir missing")?;
if !c.starts_with(&root) {
    eprintln!("{name} resolves outside the skills root (symlink?) — refusing");
}

Prevention

When it happens

Trigger: The skill directory under skills/ is a symlink to elsewhere (dotfiles managers, manual ln -s); the global skills root does not exist or is itself a dangling symlink; filesystems where canonicalization resolves differently for parent and child.

Common situations: Managing the skills dir with stow or symlinks; data_dir moved after install leaving a stale root; cross-device mounts under the skills path.

Related errors


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