{"record":{"id":"1b2be984af192476","repo":"siyuan-note/siyuan","slug":"unzip-failed-s","errorCode":null,"errorMessage":"unzip failed: %s","messagePattern":"unzip failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":440,"sourceCode":"// installFromZip 解压 zip 并安装其中的 skill\nfunc installFromZip(data []byte) (*InstallSkillResult, error) {\n\ttmpRoot := filepath.Join(TempDir, \"ai\", \"skill-install\", gulu.Rand.String(7))\n\tif err := os.MkdirAll(tmpRoot, 0755); err != nil {\n\t\treturn nil, err\n\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\")) {","sourceCodeStart":422,"sourceCodeEnd":458,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/skill.go#L422-L458","documentation":"Thrown by installFromZip when gulu.Zip.Unzip fails to extract the downloaded zip. The downloaded bytes are written to a temp file and passed to gulu's unzip, which includes zip-slip protection. Failure means the archive is corrupt, not a zip, or contains unsafe entries.","triggerScenarios":"The downloaded payload is not actually a zip despite a zip-like content-type; the archive is truncated/corrupt; it contains entries with absolute or traversal paths that gulu rejects; an incomplete download produced a partial file.","commonSituations":"Content-type lied (server labeled a tar.gz or HTML as application/zip); a mid-stream truncation (see read-body failures) left a short file; the zip was created with unusual compression gulu cannot decode.","solutions":["Download the URL manually and confirm it is a valid zip (unzip it locally).","If the source is a tar.gz, switch to a zip URL or a raw SKILL.md link.","Re-run the install to rule out a truncated download.","Provide a zip built with standard tools (avoid exotic compression formats)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"res, err := util.InstallSkill(src)\nif err != nil && strings.HasPrefix(err.Error(), \"unzip failed\") {\n    // fall back to the raw SKILL.md URL if the zip is unusable\n    res, err = util.InstallSkill(rawSkillMDURL)\n}","preventionTips":["Confirm the URL actually serves a zip by downloading it locally first.","Use standard zip tooling; avoid unsupported compression variants.","Prefer raw SKILL.md links when only a single file is needed."],"tags":["skill-management","zip","archive"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}