{"record":{"id":"b3c7b37d42fe271b","repo":"ultraworkers/claw-code","slug":"skill-directory-must-contain-skill-md","errorCode":null,"errorMessage":"skill directory '{}' must contain SKILL.md","messagePattern":"skill directory '(.+?)' must contain SKILL\\.md","errorType":"validation","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"rust/crates/commands/src/lib.rs","lineNumber":3872,"sourceCode":"\nfn resolve_skill_install_source(source: &str, cwd: &Path) -> std::io::Result<SkillInstallSource> {\n    let candidate = PathBuf::from(source);\n    let source = if candidate.is_absolute() {\n        candidate\n    } else {\n        cwd.join(candidate)\n    };\n    let source = fs::canonicalize(&source).map_err(|e| {\n        std::io::Error::new(\n            e.kind(),\n            format!(\"skill source '{}' not found: {e}\", source.display()),\n        )\n    })?;\n\n    if source.is_dir() {\n        let prompt_path = source.join(\"SKILL.md\");\n        if !prompt_path.is_file() {\n            return Err(std::io::Error::new(\n                std::io::ErrorKind::InvalidInput,\n                format!(\n                    \"skill directory '{}' must contain SKILL.md\",\n                    source.display()\n                ),\n            ));\n        }\n        return Ok(SkillInstallSource::Directory {\n            root: source,\n            prompt_path,\n        });\n    }\n\n    if source\n        .extension()\n        .is_some_and(|ext| ext.to_string_lossy().eq_ignore_ascii_case(\"md\"))\n    {\n        return Ok(SkillInstallSource::MarkdownFile { path: source });","sourceCodeStart":3854,"sourceCodeEnd":3890,"githubUrl":"https://github.com/ultraworkers/claw-code/blob/08106b0c3771ef5b4a5aa176acccd460e88b7325/rust/crates/commands/src/lib.rs#L3854-L3890","documentation":"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`.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\n/skills install ./claw-skills           # skill directory '...' must contain SKILL.md\n\n# after\n/skills install ./claw-skills/src/my-skill   # dir containing SKILL.md","handlingStrategy":"validation","validationCode":"fn is_installable_skill_dir(p: &Path) -> bool {\n    p.is_dir() && p.join(\"SKILL.md\").is_file()   // exact case: SKILL.md\n}","typeGuard":"fn classify_skill_source(p: &Path) -> Option<&'static str> {\n    if p.is_dir() {\n        if p.join(\"SKILL.md\").is_file() { Some(\"directory\") } else { None }\n    } else if p.extension().is_some_and(|e| e.to_string_lossy().eq_ignore_ascii_case(\"md\")) {\n        Some(\"markdown\")\n    } else { None }\n}","tryCatchPattern":"match resolve_skill_install_source(src, cwd) {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput\n        && e.to_string().contains(\"must contain SKILL.md\") => { /* descend into subdirs to find SKILL.md */ }\n    other => other,\n}","preventionTips":["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"],"tags":["skills","install","skill-md","missing-file"],"backgroundTag":"missing-required-file","analyzedSha":"08106b0c3771ef5b4a5aa176acccd460e88b7325","analyzedAt":"2026-08-18T00:29:38.590Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}