ultraworkers/claw-code · error · std::io::Error
skill directory '{}' must contain SKILL.md
Error message
skill directory '{}' must contain SKILL.md What it means
In `resolve_skill_install_source` (commands/src/lib.rs:3872): the canonicalized source is a directory, but `<dir>/SKILL.md` is not a regular file. A skill directory is only installable when its SKILL.md prompt sits at the directory root. Returned as `ErrorKind::InvalidInput`.
Source
Thrown at rust/crates/commands/src/lib.rs:3872
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,
prompt_path,
});
}
if source
.extension()
.is_some_and(|ext| ext.to_string_lossy().eq_ignore_ascii_case("md"))
{
return Ok(SkillInstallSource::MarkdownFile { path: source });View on GitHub (pinned to 08106b0c37)
Solutions
- Point the install at the subdirectory that directly contains SKILL.md.
- If the prompt file is misnamed, rename it to exactly `SKILL.md`.
- For single-file skills, point at the markdown file itself instead of a directory.
Example fix
# before /skills install ./claw-skills # skill directory '...' must contain SKILL.md # after /skills install ./claw-skills/src/my-skill # dir containing SKILL.md
Defensive patterns
Strategy: validation
Validate before calling
fn is_installable_skill_dir(p: &Path) -> bool {
p.is_dir() && p.join("SKILL.md").is_file() // exact case: SKILL.md
} Type guard
fn classify_skill_source(p: &Path) -> Option<&'static str> {
if p.is_dir() {
if p.join("SKILL.md").is_file() { Some("directory") } else { None }
} else if p.extension().is_some_and(|e| e.to_string_lossy().eq_ignore_ascii_case("md")) {
Some("markdown")
} else { None }
} Try / catch
match resolve_skill_install_source(src, cwd) {
Err(e) if e.kind() == std::io::ErrorKind::InvalidInput
&& e.to_string().contains("must contain SKILL.md") => { /* descend into subdirs to find SKILL.md */ }
other => other,
} Prevention
- Point installs at the dir that directly contains SKILL.md, not a collection root
- Name the prompt file exactly SKILL.md (case-sensitive)
- For single-file skills pass the .md file itself
When it happens
Trigger: Pointing `/skills install` at the root of a skill COLLECTION repo where each skill is a subdirectory; a skill dir whose prompt file is named `skill.md` (case matters), `README.md`, or `SKILL.MD`; SKILL.md being a directory or symlink to a non-file.
Common situations: Cloning a marketplace repo and installing the repo root instead of `repo/skills/my-skill`; renaming the prompt file during a migration from another convention; case-variant filenames after copying from a case-insensitive filesystem.
Related errors
- unable to resolve a skills install root; set CLAW_CONFIG_HOM
- skill source '{}' not found: {e}
- skill source '{}' must be a directory with SKILL.md or a mar
- unable to derive an installable invocation name from '{}'
AI-assisted analysis of ultraworkers/claw-code@08106b0c37 (2026-08-18).
Data as JSON: /api/errors/b3c7b37d42fe271b.
Report an issue: GitHub.