{"record":{"id":"8fa6866b882a8fbc","repo":"siyuan-note/siyuan","slug":"no-skill-md-found-in-the-archive","errorCode":null,"errorMessage":"no SKILL.md found in the archive","messagePattern":"no SKILL\\.md found in the archive","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":445,"sourceCode":"\t}\n\tdefer os.RemoveAll(tmpRoot)\n\n\tzipPath := filepath.Join(tmpRoot, \"src.zip\")\n\tif err := os.WriteFile(zipPath, data, 0644); err != nil {\n\t\treturn nil, err\n\t}\n\tunzipDir := filepath.Join(tmpRoot, \"unzip\")\n\tif err := os.MkdirAll(unzipDir, 0755); err != nil {\n\t\treturn nil, err\n\t}\n\t// gulu.Zip.Unzip 已内置 zip-slip 路径穿越防护\n\tif err := gulu.Zip.Unzip(zipPath, unzipDir); err != nil {\n\t\treturn nil, errors.New(\"unzip failed: \" + err.Error())\n\t}\n\n\tskillDirs := findSkillDirs(unzipDir)\n\tif len(skillDirs) == 0 {\n\t\treturn nil, errors.New(\"no SKILL.md found in the archive\")\n\t}\n\treturn installSkillDirs(skillDirs, unzipDir)\n}\n\n// findSkillDirs 在解压根下查找含 SKILL.md 的 skill 目录，返回相对 root 的路径。\n// 递归下钻以兼容任意包裹层（codeload 会把仓库内容包在 <repo-name>/ 下），\n// 但一旦某个目录被认定为 skill（直接含 SKILL.md）就停止下钻，避免误入 skill 内部的\n// references/scripts 等子目录。识别的结构：\n//   - SKILL.md 直接在 root（无包裹）\n//   - <wrap>/SKILL.md（单层或多层包裹的单 skill）\n//   - <wrap>/skills/<name>/SKILL.md（集合仓库，wrap 可有可无）\nfunc findSkillDirs(root string) []string {\n\tif gulu.File.IsExist(filepath.Join(root, \"SKILL.md\")) {\n\t\treturn []string{\".\"}\n\t}\n\treturn findSkillDirsRecursive(root, root)\n}\n","sourceCodeStart":427,"sourceCodeEnd":463,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/skill.go#L427-L463","documentation":"Thrown by installFromZip after a successful unzip when findSkillDirs finds no directory containing SKILL.md. The recursive search honors wrapper directories and a skills/<name> layout, but if no SKILL.md exists anywhere it gives up.","triggerScenarios":"The repository/archive does not contain a SKILL.md at any expected location; the skill file is named differently (skill.md, README.md); SKILL.md is nested under a skipped directory (.github, node_modules, .git, .idea).","commonSituations":"Installing a generic repo that is not a skill; the skill file uses a non-standard name; the SKILL.md is under node_modules or .github which findSkillDirs deliberately skips.","solutions":["Ensure the source repository contains a SKILL.md at its root, in a wrapper dir, or under skills/<name>/.","Rename the skill manifest to exactly SKILL.md (case-sensitive).","Move SKILL.md out of skipped directories (.github, node_modules, .git, .idea).","For a single-file skill, use the raw SKILL.md URL instead of a zip."],"exampleFix":"// before — repo has skill.md (lowercase) and is unzipped\n// after — rename the manifest to SKILL.md in the source repo, then:\nutil.InstallSkill(\"owner/repo\")","handlingStrategy":"validation","validationCode":"// before offering install, validate the repo layout remotely if possible\n// ensure a SKILL.md exists at root, <wrap>/, or <wrap>/skills/<name>/","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Name the manifest exactly SKILL.md (case-sensitive).","Do not place SKILL.md under .github, node_modules, .git, or .idea — these are skipped.","Use the raw SKILL.md URL for single-file skills that have no wrapper repo."],"tags":["skill-management","zip","manifest"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}