{"record":{"id":"7c57173fc7fe411a","repo":"siyuan-note/siyuan","slug":"download-failed-s","errorCode":null,"errorMessage":"download failed: %s","messagePattern":"download failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":403,"sourceCode":"\t// codeload main 分支 404 时回退 master\n\tif src.isZip && src.branch == \"main\" {\n\t\townerRepo := strings.TrimPrefix(strings.TrimPrefix(src.downloadURL, \"https://codeload.github.com/\"), \"http://codeload.github.com/\")\n\t\townerRepo = strings.TrimSuffix(ownerRepo, \"/zip/refs/heads/main\")\n\t\tfallback := codeloadSource(ownerRepo, \"master\")\n\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","sourceCodeStart":385,"sourceCodeEnd":421,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/skill.go#L385-L421","documentation":"Thrown by fetchBytes when httpclient.NewBrowserRequest().Get returns a non-nil error before any response is obtained. This is a transport-level failure (DNS, connection refused, TLS handshake, timeout, proxy), not an HTTP status error.","triggerScenarios":"DNS resolution fails for the host; the server is unreachable (connection refused/timeout); TLS certificate is invalid; a proxy blocks the request; the local network is down.","commonSituations":"Air-gapped or proxied environments; self-signed or expired TLS certs; corporate firewall blocking codeload/raw.githubusercontent.com; transient connectivity loss.","solutions":["Check network connectivity and DNS resolution for the target host.","Verify proxy/TLS configuration in the environment (httpclient honors standard proxy env vars).","Retry with exponential backoff for transient failures.","For TLS errors, confirm the system CA store is up to date."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// retry transient transport errors with backoff\nvar res *util.InstallSkillResult\nvar err error\nfor attempt := 0; attempt < 3; attempt++ {\n    res, err = util.InstallSkill(src)\n    if err == nil || !strings.HasPrefix(err.Error(), \"download failed:\") {\n        break\n    }\n    time.Sleep(time.Duration(1<<attempt) * time.Second)\n}","preventionTips":["Ensure outbound HTTPS to codeload.github.com and raw.githubusercontent.com is permitted.","Configure proxy settings via standard HTTP_PROXY/HTTPS_PROXY env vars when behind a corporate proxy.","Keep the system CA store current to avoid TLS handshake failures."],"tags":["skill-management","network","transport"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}