{"record":{"id":"ed5c53efba509a2e","repo":"siyuan-note/siyuan","slug":"unzip-failed","errorCode":null,"errorMessage":"unzip failed: ","messagePattern":"unzip failed: ","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":736,"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":718,"sourceCodeEnd":754,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/util/skill.go#L718-L754","documentation":"installFromZip writes the downloaded bytes to a temp file and extracts it with gulu.Zip.Unzip (which includes zip-slip protection). Any extraction failure — corrupted archive, non-zip content, unsupported compression — is wrapped as \"unzip failed: <underlying error>\".","triggerScenarios":"The bytes passed to installFromZip are not a valid zip: the URL returned an HTML error/login page with 200, the download was truncated, the file is gzip/tar instead of zip, or the archive uses an unsupported compression method or is password-protected.","commonSituations":"Pastebin/proxy URLs that serve HTML with status 200; manually renamed .tar.gz files; corrupted downloads over flaky networks; archives produced with non-standard zip tooling; content behind a redirect that yields a login page.","solutions":["Confirm the source URL returns an actual application/zip payload (curl -sI <url> and check Content-Type and size)","Check the wrapped underlying error: 'zip: not a valid zip file' means wrong content; 'unexpected EOF' means truncation — re-download","Recreate the archive with a standard zip tool without encryption or exotic compression","Test unzipping the file locally before installing"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const buf = await fetch(url).then(r => r.arrayBuffer()); if (new Uint8Array(buf.slice(0,2)).join(\",\") !== \"80,75\") throw new Error(\"not a zip file (missing PK header)\");","typeGuard":"function looksLikeZip(bytes) { return bytes && bytes.length > 4 && bytes[0] === 0x50 && bytes[1] === 0x4b; }","tryCatchPattern":"try { await installSkill(src); } catch (e) { if (/unzip failed: zip: not a valid zip/.test(e.message)) { verifyUrlServesZip(); } else throw e; }","preventionTips":["Confirm the URL serves application/zip, not an HTML error page with status 200","Unzip the archive locally with a standard tool before distributing it","Avoid password-protected or exotic-compression archives","Re-download if the transfer may have been truncated"],"tags":["zip","archive","validation","download"],"backgroundTag":"invalid-argument-format","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}