siyuan-note/siyuan · error
no SKILL.md found in the archive
Error message
no SKILL.md found in the archive
What it means
After unzipping, installFromZip searches the archive recursively for directories containing a SKILL.md (tolerating codeload's top-level <repo>/ wrapper and skills/ containers). If no directory qualifies, the archive contains no installable skill and the install aborts with this error.
Solutions
- Ensure the archive contains a file named exactly SKILL.md (uppercase, no extension variants)
- Place SKILL.md at the archive root, one wrapper level down, or under <wrap>/skills/<name>/SKILL.md
- Verify you are installing a skill package, not an arbitrary repo
- Extract the zip locally and check the layout matches the documented structures in findSkillDirs
Example fix
// before my-skill.zip └── README.md (no SKILL.md) // after my-skill.zip └── my-skill/ └── SKILL.md
Defensive patterns
Strategy: validation
Validate before calling
// Inspect the zip locally before installing
const entries = await listZipEntries(zipBytes)
const hasSkill = entries.some(n => /(^|\/)SKILL\.md$/.test(n))
if (!hasSkill) throw new Error("archive has no SKILL.md; not a skill package") Try / catch
try {
await installSkill(zipUrl)
} catch (e) {
if (String(e).includes("no SKILL.md found")) {
showHint("The archive must contain SKILL.md (exact name) at root, under a wrapper dir, or under <wrap>/skills/<name>/")
}
} Prevention
- Name the skill manifest exactly SKILL.md (case-sensitive on Linux)
- Follow the recognized layouts: root SKILL.md, <wrap>/SKILL.md, or <wrap>/skills/<name>/SKILL.md
- Confirm the target repo is actually a skill package before installing
When it happens
Trigger: The downloaded zip contains no SKILL.md file at any searchable depth — e.g. a random repo, an archive whose skill is named differently (skill.md, Skill.md), or an archive that only holds source files.
Common situations: Pointing InstallSkill at a regular code repo that is not a skill package; a case-sensitive mismatch (SKILL.MD, skill.md) on Linux; archive uses a nested structure the finder's stop-on-skill rule excludes (SKILL.md deeper inside an already-identified skill); empty zip.
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
- 199
- Conf.Language(199)
- Conf.Language(199)
- invalid github URL
- marketplace package manifest must be at the archive root or…
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/8fa6866b882a8fbc.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/skill.go:747
}
defer os.RemoveAll(tmpRoot)
zipPath := filepath.Join(tmpRoot, "src.zip")
if err := os.WriteFile(zipPath, data, 0644); err != nil {
return nil, err
}
unzipDir := filepath.Join(tmpRoot, "unzip")
if err := os.MkdirAll(unzipDir, 0755); err != nil {
return nil, err
}
// gulu.Zip.Unzip 已内置 zip-slip 路径穿越防护
if err := gulu.Zip.Unzip(zipPath, unzipDir); err != nil {
return nil, errors.New("unzip failed: " + err.Error())
}
skillDirs := findSkillDirs(unzipDir)
if len(skillDirs) == 0 {
return nil, errors.New("no SKILL.md found in the archive")
}
return installSkillDirs(skillDirs, unzipDir)
}
// findSkillDirs 在解压根下查找含 SKILL.md 的 skill 目录,返回相对 root 的路径。
// 递归下钻以兼容任意包裹层(codeload 会把仓库内容包在 <repo-name>/ 下),
// 但一旦某个目录被认定为 skill(直接含 SKILL.md)就停止下钻,避免误入 skill 内部的
// references/scripts 等子目录。识别的结构:
// - SKILL.md 直接在 root(无包裹)
// - <wrap>/SKILL.md(单层或多层包裹的单 skill)
// - <wrap>/skills/<name>/SKILL.md(集合仓库,wrap 可有可无)
func findSkillDirs(root string) []string {
if gulu.File.IsExist(filepath.Join(root, "SKILL.md")) {
return []string{"."}
}
return findSkillDirsRecursive(root, root)
}
View on GitHub (pinned to 9f775e8a12)