{"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":414,"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":396,"sourceCodeEnd":432,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/skill.go#L396-L432","documentation":"Thrown by fetchBytes when io.ReadAll fails while streaming the response body (after headers were received and status was < 400). The body read is capped at maxSkillDownloadBytes+1 via io.LimitReader. This indicates the connection broke mid-transfer or the reader errored.","triggerScenarios":"The server closed the connection before sending the full body; a network drop during a large download; an io error from the LimitReader; the response uses chunked encoding that terminates prematurely.","commonSituations":"Unstable connection on large zip archives; mobile/flaky network; server-side timeouts on slow transfers; intermediary (proxy/CDN) resetting the connection.","solutions":["Retry the install; transient mid-stream failures often succeed on a second attempt.","Switch to a smaller or single-file SKILL.md source to reduce transfer size.","Stabilize the network connection or disable an aggressive proxy.","If persistent, fetch the URL directly with curl to isolate server vs. client."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// retry mid-stream read failures a limited number of times\nfor i := 0; i < 2; i++ {\n    res, err = util.InstallSkill(src)\n    if err == nil || !strings.HasPrefix(err.Error(), \"read body failed\") {\n        break\n    }\n}","preventionTips":["Prefer smaller sources (single-file SKILL.md or a narrow subdirectory) on flaky networks.","Retry once before reporting; body-read errors are often transient.","Isolate persistent failures by fetching the URL with curl from the same host."],"tags":["skill-management","network","io"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}