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

  1. Ensure the archive contains a file named exactly SKILL.md (uppercase, no extension variants)
  2. Place SKILL.md at the archive root, one wrapper level down, or under <wrap>/skills/<name>/SKILL.md
  3. Verify you are installing a skill package, not an arbitrary repo
  4. 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

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


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)