{"record":{"id":"2006269e1077bf57","repo":"siyuan-note/siyuan","slug":"read-body-failed-s-200626","errorCode":null,"errorMessage":"read body failed: %s","messagePattern":"read body failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":716,"sourceCode":"\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\n\t}\n\tdefer os.RemoveAll(tmpRoot)\n\n\tzipPath := filepath.Join(tmpRoot, \"src.zip\")\n\tif err := os.WriteFile(zipPath, data, 0644); err != nil {\n\t\treturn nil, err","sourceCodeStart":698,"sourceCodeEnd":734,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/skill.go#L698-L734","documentation":"fetchBytes reads the response body through io.LimitReader capped at maxSkillDownloadBytes+1; if the read itself errors (connection reset mid-transfer, timeout while streaming, decompression failure), it returns 'read body failed: <cause>'. The download started successfully but the body could not be fully received.","triggerScenarios":"io.ReadAll fails mid-stream after a 2xx response — server closed the connection prematurely, network dropped during transfer, or the underlying reader returned a non-EOF error.","commonSituations":"Flaky Wi-Fi or mobile connection during a large download; proxy dropping long-running transfers; server-side aborts; TLS truncation.","solutions":["Inspect the wrapped cause and simply retry the install — most mid-transfer failures are transient","Check network stability; prefer wired/stable connections for large archives","If a proxy is in the path, check its idle/transfer timeouts","If it persists for one host only, try an alternative mirror or direct URL for the same skill"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await installSkill(src)\n} catch (e) {\n  if (String(e).startsWith(\"read body failed:\")) {\n    await retryWithBackoff(() => installSkill(src), { retries: 3 })\n  }\n}","preventionTips":["Retry mid-transfer read failures — they are almost always transient","Avoid installing over unstable networks; prefer stable connections for multi-MB archives","If failures cluster behind one proxy, raise its transfer timeouts"],"tags":["network","io","download"],"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"}