siyuan-note/siyuan · error
read body failed
Error message
read body failed: %s
What it means
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.
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
Defensive patterns
Strategy: retry
Try / catch
try {
await installSkill(src)
} catch (e) {
if (String(e).startsWith("read body failed:")) {
await retryWithBackoff(() => installSkill(src), { retries: 3 })
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Flaky Wi-Fi or mobile connection during a large download; proxy dropping long-running transfers; server-side aborts; TLS truncation.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- read body failed:
- read custom emoji response failed
- download custom emoji failed
- download custom emoji failed with status
- download failed:
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/2006269e1077bf57.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/skill.go:716
return nil, "", err
}
// fetchBytes 执行带大小限制的 GET
func fetchBytes(rawURL string) (data []byte, contentType string, err error) {
resp, err := httpclient.NewBrowserRequest().Get(rawURL)
if err != nil {
return nil, "", errors.New("download failed: " + err.Error())
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return nil, "", fmt.Errorf("download failed: HTTP %d", resp.StatusCode)
}
contentType = resp.Header.Get("Content-Type")
body, err := io.ReadAll(io.LimitReader(resp.Body, maxSkillDownloadBytes+1))
if err != nil {
return nil, "", errors.New("read body failed: " + err.Error())
}
if len(body) > maxSkillDownloadBytes {
return nil, "", errors.New("skill source too large (limit 10MB)")
}
return body, contentType, nil
}
// installFromZip 解压 zip 并安装其中的 skill
func installFromZip(data []byte) (*InstallSkillResult, error) {
tmpRoot := filepath.Join(TempDir, "ai", "skill-install", gulu.Rand.String(7))
if err := os.MkdirAll(tmpRoot, 0755); err != nil {
return nil, err
}
defer os.RemoveAll(tmpRoot)
zipPath := filepath.Join(tmpRoot, "src.zip")
if err := os.WriteFile(zipPath, data, 0644); err != nil {
return nil, errView on GitHub (pinned to 9f775e8a12)