ultraworkers/claw-code · error · std::io::Error

skill source '{}' not found: {e}

Error message

skill source '{}' not found: {e}

What it means

`resolve_skill_install_source` (commands/src/lib.rs:3863) builds a candidate from the `/skills install <source>` argument — absolute as-is, otherwise joined onto the handler's `cwd` (the process working directory, not your shell's notion of 'here') — then `fs::canonicalize` fails. The error re-wraps the OS error kind (NotFound, PermissionDenied, etc.) with the candidate path in the message.

Source

Thrown at rust/crates/commands/src/lib.rs:3863

    }
    if let Some(home) = env::var_os("HOME") {
        return Ok(PathBuf::from(home).join(".claw").join("skills"));
    }
    Err(std::io::Error::new(
        std::io::ErrorKind::NotFound,
        "unable to resolve a skills install root; set CLAW_CONFIG_HOME or HOME",
    ))
}

fn resolve_skill_install_source(source: &str, cwd: &Path) -> std::io::Result<SkillInstallSource> {
    let candidate = PathBuf::from(source);
    let source = if candidate.is_absolute() {
        candidate
    } else {
        cwd.join(candidate)
    };
    let source = fs::canonicalize(&source).map_err(|e| {
        std::io::Error::new(
            e.kind(),
            format!("skill source '{}' not found: {e}", source.display()),
        )
    })?;

    if source.is_dir() {
        let prompt_path = source.join("SKILL.md");
        if !prompt_path.is_file() {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                format!(
                    "skill directory '{}' must contain SKILL.md",
                    source.display()
                ),
            ));
        }
        return Ok(SkillInstallSource::Directory {
            root: source,

View on GitHub (pinned to 08106b0c37)

Solutions

  1. Pass an absolute path to `/skills install`.
  2. Verify the path exists from the same cwd the claw process was started in (`pwd; ls <source>`).
  3. Check the underlying kind in the message — PermissionDenied means a parent dir traversal problem, not a typo.

Example fix

# before
/skills install my-skill          # skill source 'my-skill' not found ...

# after
/skills install /home/me/skills/my-skill
Defensive patterns

Strategy: validation

Validate before calling

// mirror resolve_skill_install_source's resolution before calling install
let candidate = Path::new(source);
let candidate = if candidate.is_absolute() { candidate.to_path_buf() } else { cwd.join(candidate) };
if !candidate.exists() { /* fix the path, don't call install */ }

Try / catch

if let Err(e) = install_skill(source, cwd) {
    if e.to_string().contains("skill source") && e.to_string().contains("not found") {
        // re-resolve as absolute path and retry
    }
}

Prevention

When it happens

Trigger: Passing a relative skill path to `/skills install` while the claw process runs with a different cwd; a typo in the path; the source being deleted between typing and executing; parent directory unreadable (canonicalize returns PermissionDenied).

Common situations: Installing from a path that is relative to the directory where claw was launched, not the directory the user thinks they are in; paths copied with a trailing `.` or wrong quoting; network-mounted homes where canonicalize can fail transiently.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of ultraworkers/claw-code@08106b0c37 (2026-08-18). Data as JSON: /api/errors/27e6449f659e4c10. Report an issue: GitHub.