siyuan-note/siyuan · error
download failed: HTTP
Error message
download failed: HTTP %d
What it means
fetchBytes performs a size-limited GET for skill source downloads (e.g. GitHub codeload zips or raw SKILL.md). When the HTTP response status is >= 400 (client or server error), it aborts with "download failed: HTTP <status>" instead of reading the body. This is a guard against processing error responses as installable content.
Solutions
- Verify the source URL is reachable in a browser or with curl -I and returns 200
- Check the repo exists, is public, and its default branch is main or master
- Wait out GitHub rate limits or use an authenticated environment if rate-limited (429/403)
- Inspect the numeric HTTP status in the message to distinguish 404 (wrong source) from 403/429 (auth/rate limit) from 5xx (server problem)
Defensive patterns
Strategy: try-catch
Validate before calling
const u = new URL(src); if (!/codeload\.github\.com$|^raw\./.test(u.hostname) || !u.pathname) throw new Error("bad skill source URL"); Type guard
function isHttpOk(status) { return typeof status === "number" && status >= 200 && status < 400; } Try / catch
try { const data = await installSkill(src); } catch (e) { if (/download failed: HTTP (4\d\d)/.test(e.message)) { const code = +e.message.match(/HTTP (\d+)/)[1]; if (code === 404) checkRepoExists(); else if (code === 429) backoffAndRetry(); } else throw e; } Prevention
- Verify the repo URL resolves publicly before installing
- Watch for GitHub rate limits when installing many skills anonymously
- Pin skill sources to existing branches (check default branch is main or master)
- Prefer official/verified skill repositories
When it happens
Trigger: Calling downloadSkillSource -> fetchBytes with a URL that returns a 4xx/5xx status: nonexistent GitHub repo or branch (404), private repo without credentials (404/403), rate-limited request (403/429), or server-side error (5xx). For codeload main-branch zips a fallback to master is attempted before this surfaces.
Common situations: Typo in repo owner/name; installing from a private or deleted repository; GitHub API/codeload rate limiting on anonymous requests; default branch renamed so neither main nor master exists; corporate proxy blocking codeload.github.com.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- download custom emoji failed
- download custom emoji failed with status
- download failed:
- download failed
- download failed (tried main and master)
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/5fc586aa309a52e9.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/skill.go:704
data, contentType, ferr := fetchBytes(fallback.downloadURL)
if ferr != nil {
return nil, "", fmt.Errorf("download failed (tried main and master): %v", err)
}
return data, contentType, nil
}
return nil, "", err
}
// fetchBytes 执行带大小限制的 GET
func fetchBytes(rawURL string) (data []byte, contentType string, err error) {
resp, err := httpclient.NewBrowserRequest().Get(rawURL)
if err != nil {
return nil, "", errors.New("download failed: " + err.Error())
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return nil, "", fmt.Errorf("download failed: HTTP %d", resp.StatusCode)
}
contentType = resp.Header.Get("Content-Type")
body, err := io.ReadAll(io.LimitReader(resp.Body, maxSkillDownloadBytes+1))
if err != nil {
return nil, "", errors.New("read body failed: " + err.Error())
}
if len(body) > maxSkillDownloadBytes {
return nil, "", errors.New("skill source too large (limit 10MB)")
}
return body, contentType, nil
}
// 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, errView on GitHub (pinned to 8641553a1f)