siyuan-note/siyuan · error
read body failed:
Error message
read body failed:
What it means
fetchBytes reads the download response body with io.ReadAll over an io.LimitReader. If the body stream fails mid-read (connection reset, timeout, truncated transfer), the error is wrapped as "read body failed: <underlying error>". The underlying transport error is appended so the real cause is visible.
Solutions
- Read the wrapped underlying error to identify the transport cause (reset vs timeout vs TLS)
- Retry the download; transient resets usually succeed on a second attempt
- Check proxy/VPN/firewall stability, especially for large archives
- Download on a stable connection or reduce the source size if it consistently truncates
Defensive patterns
Strategy: retry
Validate before calling
const resp = await fetch(url, {method: "HEAD"}); if (!resp.ok) throw new Error("source not reachable"); Try / catch
try { await installSkill(src); } catch (e) { if (/read body failed/.test(e.message)) { await retryWithBackoff(() => installSkill(src), 3); } else throw e; } Prevention
- Download over stable connections; avoid flaky Wi-Fi/VPN for large archives
- Retry transient network failures with exponential backoff
- Check proxy/firewall stability if truncation repeats on the same source
- Keep sources small so transfer windows are short
When it happens
Trigger: Network interruption while streaming a skill zip from codeload.github.com or a raw URL: connection reset by peer, TLS handshake drop, context/proxy timeout, or malformed chunked encoding during the GET initiated by httpclient.NewBrowserRequest().Get.
Common situations: Unstable Wi-Fi or mobile connections; aggressive corporate proxies or firewalls killing long transfers; GitHub closing connections on very large archives; VPN drops mid-download.
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@8641553a1f (2026-09-11).
Data as JSON: /api/errors/c21eba83c1877ce5.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/skill.go:710
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 8641553a1f)