siyuan-note/siyuan · error
unzip failed:
Error message
unzip failed:
What it means
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>".
Source
Thrown at kernel/util/skill.go:736
// installFromZip 解压 zip 并安装其中的 skill
func installFromZip(data []byte) (*InstallSkillResult, error) {
tmpRoot := filepath.Join(TempDir, "ai", "skill-install", gulu.Rand.String(7))
if err := os.MkdirAll(tmpRoot, 0755); err != nil {
return nil, err
}
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")) {View on GitHub (pinned to 8641553a1f)
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
Defensive patterns
Strategy: validation
Validate before calling
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)"); Type guard
function looksLikeZip(bytes) { return bytes && bytes.length > 4 && bytes[0] === 0x50 && bytes[1] === 0x4b; } Try / catch
try { await installSkill(src); } catch (e) { if (/unzip failed: zip: not a valid zip/.test(e.message)) { verifyUrlServesZip(); } else throw e; } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Conf.Language(199)
- 199
- unzip failed: %s
- invalid marketplace package archive
- marketplace package archive is empty
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/ed5c53efba509a2e.
Report an issue: GitHub.