{"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":408,"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":390,"sourceCodeEnd":426,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/skill.go#L390-L426","documentation":"Thrown by fetchBytes when the HTTP response status code is 400 or above. The status number is formatted into the message. It surfaces server-side rejections (not found, forbidden, rate limit, server error) distinctly from transport errors.","triggerScenarios":"404 for a non-existent repo/file/branch; 403 or 401 for private repos or rate limiting; 5xx for upstream outages; release asset URL returning 404 because the tag/asset name is wrong.","commonSituations":"Wrong owner/repo or branch (404); GitHub rate limit on unauthenticated codeload (403); private repo (404/401); a release asset renamed or removed; GitHub incident (5xx).","solutions":["404: verify owner/repo, branch name, and file path; prefer the tree/<branch> URL form.","403: reduce request frequency, wait out the rate-limit window, or authenticate via a token-bearing URL where supported.","5xx: retry after a short delay; check status.github.com.","For release assets, confirm the exact tag and asset filename from the releases page."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"res, err := util.InstallSkill(src)\nif err != nil {\n    if strings.Contains(err.Error(), \"HTTP 404\") {\n        return errors.New(\"skill source not found; check the owner/repo, branch, and path\")\n    }\n    if strings.Contains(err.Error(), \"HTTP 403\") {\n        return errors.New(\"access denied or rate-limited by GitHub; wait and retry, or use an authenticated URL\")\n    }\n    return err\n}","preventionTips":["Verify owner/repo and branch spellings before installing.","Cache successful downloads to reduce repeated hits that trigger rate limits.","For release assets, confirm the exact tag and filename from the releases page."],"tags":["skill-management","network","http-status"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}