zeroclaw-labs/zeroclaw · error · anyhow::Error
skill catalog {$url} has a skills/ directory that resolves o
Error message
skill catalog {$url} has a skills/ directory that resolves outside the cloned catalog; refusing to inspect it What it means
Defense-in-depth after the symlink check: the canonicalized skills/ root must remain inside the canonicalized clone root (Path::starts_with on canonical paths). If canonicalization resolves outside — e.g. the entry was swapped for a symlink between the metadata check and canonicalize (TOCTOU), or exotic mount/aliasing behavior — the install aborts rather than inspect foreign directories.
Source
Thrown at crates/zeroclaw-runtime/src/skills/mod.rs:2425
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(
"cli-skills-install-skill-not-in-catalog-empty",
&[("skill", skill_name), ("url", url)]
));
}
let skill_dir = skills_root.join(skill_name);
let entry_meta = match std::fs::symlink_metadata(&skill_dir) {
Ok(metadata) => metadata,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
let available = list_contained_catalog_skill_names(&skills_root);
if available.is_empty() {
anyhow::bail!(crate::i18n::get_required_cli_string_with_args(View on GitHub (pinned to 88bb9c8533)
Solutions
- Retry the install — transient races resolve on a second attempt
- If persistent, clone the catalog manually and inspect skills/ with ls -la and realpath
- Treat a persistent case as a hostile or broken catalog and stop using that URL
Defensive patterns
Strategy: retry
Try / catch
match install_git_catalog_skill_source(url, skill, &skills_path, false, &ws) {
Err(e) if e.to_string().contains("resolves outside the cloned catalog") => {
// likely a TOCTOU race: retry once; if it persists, audit the catalog
// (clone manually, ls -la skills, realpath skills) and drop the URL
}
r => r,
} Prevention
- Retry once on this error — races resolve; persistent hits mean a hostile catalog
- Pin catalog URLs to known-good commits or trusted hosts
- Avoid installing catalogs while other processes modify the same workspace
When it happens
Trigger: The skills entry replaced by an out-of-tree symlink between the two checks; unusual filesystem layouts where canonical paths diverge from the clone root.
Common situations: Rare in practice: races during install or adversarial catalogs. A plain retry almost always succeeds.
Related errors
- skill '{$skill}' in {$url} is a symlink; catalog skills must
- skill '{$skill}' in {$url} resolves outside the cloned catal
- skill catalog {$url} has a symlinked skills/ directory; refu
- Invalid skill name: {name}
- Lark/Feishu marker target resolves outside workspace_dir
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/2e8c3e0cd72f364b.
Report an issue: GitHub.