siyuan-note/siyuan · error
unsupported skill source
Error message
unsupported skill source (content-type: %s); expected a zip archive or a SKILL.md text file
What it means
After downloading the skill source, InstallSkill inspects the HTTP Content-Type (and a leading '---' fallback for frontmatter). If the payload is neither a zip archive nor SKILL.md-style text, it refuses to install with this error naming the observed content-type.
Solutions
- Use a raw.githubusercontent.com URL for SKILL.md or a direct .zip link instead of an HTML page URL
- Ensure release assets are zip archives (not tar.gz or bare binaries)
- Check the URL in a browser/curl to see what content-type it actually returns
- If the server sends a wrong MIME type but the content is valid SKILL.md text, ensure the body starts with '---'
Example fix
// before
InstallSkill("https://github.com/owner/repo/blob/main/SKILL.md") // HTML page
// after
InstallSkill("https://raw.githubusercontent.com/owner/repo/main/SKILL.md") Defensive patterns
Strategy: validation
Validate before calling
const res = await fetch(url, { method: "HEAD" });
const ct = res.headers.get("content-type") || "";
const isZip = ct.includes("zip") || url.endsWith(".zip");
const isText = ct.startsWith("text/") || url.endsWith("SKILL.md");
if (!isZip && !isText) throw new Error(`unsupported content-type: ${ct}`); Try / catch
try {
await installSkill(url);
} catch (e) {
if (String(e.message).startsWith("unsupported skill source")) {
// retry with a raw.githubusercontent.com URL or a zip asset
}
} Prevention
- Use raw file URLs, not HTML page URLs
- Ensure release assets are zip archives
- Pre-check content-type with a HEAD request for automation
When it happens
Trigger: Downloading a URL that returns a non-zip, non-text content type — e.g. an HTML error page (text/html is caught as text only if it starts with '---'; HTML pages typically won't), a binary release asset that isn't a zip, or a server returning application/octet-stream for a non-zip file.
Common situations: Pointing the installer at a GitHub web page URL instead of the raw file; a release asset that is a tar.gz rather than zip; rate-limit or login pages served with unexpected content types; misconfigured CDN returning wrong MIME type.
Related errors
- custom emoji file is too large
- download custom emoji failed
- download custom emoji failed with status
- download failed:
- download failed: HTTP
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/42aef9e191a5be28.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/skill.go:581
data, contentType, err := downloadSkillSource(src)
if err != nil {
return nil, err
}
// 按内容类型或来源判定处理方式
isZip := src.isZip || strings.HasPrefix(contentType, "application/zip") ||
strings.HasPrefix(contentType, "application/x-zip-compressed")
if isZip {
return installFromZip(data)
}
// 文本:当作单个 SKILL.md
if strings.HasPrefix(contentType, "text/") || strings.HasPrefix(strings.TrimSpace(string(data)), "---") {
return installFromSingleSkillMD(data)
}
return nil, fmt.Errorf("unsupported skill source (content-type: %s); expected a zip archive or a SKILL.md text file", contentType)
}
// normalizeSkillURL 把各种输入归一化为下载源
func normalizeSkillURL(raw string) (normalizedSkillSource, error) {
raw = strings.TrimSpace(raw)
// 1. 整条 "npx skills add owner/repo ..." 命令:提取 owner/repo
if strings.Contains(raw, "skills add") || strings.Contains(raw, "skills@") {
if m := skillsAddPattern.FindStringSubmatch(raw); len(m) == 2 {
return codeloadSource(m[1], "main"), nil
}
}
// 2. owner/repo 简写(无 scheme、无点、单个 /)
if !strings.Contains(raw, "://") && !strings.Contains(raw, "//") && ownerRepoPattern.MatchString(raw) {
return codeloadSource(raw, "main"), nil
}
View on GitHub (pinned to 9f775e8a12)