{"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":705,"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":687,"sourceCodeEnd":723,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/skill.go#L687-L723","documentation":"fetchBytes wraps transport-level failures of the HTTP GET it performs to download the skill source. Any error returned by the HTTP client itself (DNS failure, TLS error, connection refused/timeout, invalid URL rejected at request time) is reported as 'download failed: <underlying error>'.","triggerScenarios":"downloadSkillSource calls fetchBytes and httpclient.NewBrowserRequest().Get(rawURL) returns a non-nil error — no HTTP response was received at all.","commonSituations":"No internet connection or offline environment; DNS resolution failure for codeload.github.com or the custom host; corporate proxy/firewall blocking the request; TLS certificate problems; request timeouts.","solutions":["Read the wrapped cause after 'download failed:' to identify the transport issue and fix connectivity accordingly","Verify network access and DNS for the source host (e.g. curl the download URL from the same machine)","Configure proxy environment variables (HTTP_PROXY/HTTPS_PROXY) if the host is only reachable via a proxy","Retry on transient errors; for persistent failures switch to a mirrored or alternative source URL"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-check reachability before install\nconst r = await fetch(downloadUrl, { method: \"HEAD\" }).catch(() => null)\nif (!r) throw new Error(\"host unreachable; check network/DNS/proxy\")","typeGuard":null,"tryCatchPattern":"try {\n  await installSkill(src)\n} catch (e) {\n  if (String(e).startsWith(\"download failed:\")) {\n    await retryWithBackoff(() => installSkill(src), { retries: 3 })\n  }\n}","preventionTips":["Ensure the deployment has outbound HTTPS access (allowlist codeload.github.com, raw.githubusercontent.com)","Configure HTTP_PROXY/HTTPS_PROXY where direct egress is blocked","Distinguish transport errors from HTTP-status errors before deciding to retry"],"tags":["network","http","download","connectivity"],"backgroundTag":"http-request-failed","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}