{"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":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":747,"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":729,"sourceCodeEnd":765,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/skill.go#L729-L765","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nmy-skill.zip └── README.md            (no SKILL.md)\n// after\nmy-skill.zip └── my-skill/  └── SKILL.md","handlingStrategy":"validation","validationCode":"// Inspect the zip locally before installing\nconst entries = await listZipEntries(zipBytes)\nconst hasSkill = entries.some(n => /(^|\\/)SKILL\\.md$/.test(n))\nif (!hasSkill) throw new Error(\"archive has no SKILL.md; not a skill package\")","typeGuard":null,"tryCatchPattern":"try {\n  await installSkill(zipUrl)\n} catch (e) {\n  if (String(e).includes(\"no SKILL.md found\")) {\n    showHint(\"The archive must contain SKILL.md (exact name) at root, under a wrapper dir, or under <wrap>/skills/<name>/\")\n  }\n}","preventionTips":["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"],"tags":["zip","skill-install","validation","file-not-found"],"backgroundTag":"file-not-found","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}