{"record":{"id":"5fc586aa309a52e9","repo":"siyuan-note/siyuan","slug":"download-failed-http-d","errorCode":null,"errorMessage":"download failed: HTTP %d","messagePattern":"download failed: HTTP (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":704,"sourceCode":"\t\tdata, contentType, ferr := fetchBytes(fallback.downloadURL)\n\t\tif ferr != nil {\n\t\t\treturn nil, \"\", fmt.Errorf(\"download failed (tried main and master): %v\", err)\n\t\t}\n\t\treturn data, contentType, nil\n\t}\n\treturn nil, \"\", err\n}\n\n// fetchBytes 执行带大小限制的 GET\nfunc fetchBytes(rawURL string) (data []byte, contentType string, err error) {\n\tresp, err := httpclient.NewBrowserRequest().Get(rawURL)\n\tif err != nil {\n\t\treturn nil, \"\", errors.New(\"download failed: \" + err.Error())\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode >= 400 {\n\t\treturn nil, \"\", fmt.Errorf(\"download failed: HTTP %d\", resp.StatusCode)\n\t}\n\n\tcontentType = resp.Header.Get(\"Content-Type\")\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, maxSkillDownloadBytes+1))\n\tif err != nil {\n\t\treturn nil, \"\", errors.New(\"read body failed: \" + err.Error())\n\t}\n\tif len(body) > maxSkillDownloadBytes {\n\t\treturn nil, \"\", errors.New(\"skill source too large (limit 10MB)\")\n\t}\n\treturn body, contentType, nil\n}\n\n// 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","sourceCodeStart":686,"sourceCodeEnd":722,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/util/skill.go#L686-L722","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const u = new URL(src); if (!/codeload\\.github\\.com$|^raw\\./.test(u.hostname) || !u.pathname) throw new Error(\"bad skill source URL\");","typeGuard":"function isHttpOk(status) { return typeof status === \"number\" && status >= 200 && status < 400; }","tryCatchPattern":"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; }","preventionTips":["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"],"tags":["network","http","download","github"],"backgroundTag":"http-error-response","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}