siyuan-note/siyuan · error
read body failed
Error message
read body failed: %s
What it means
Reading the response body failed mid-stream: io.ReadAll over io.LimitReader returned an error, and the suffix carries it. Headers and status were already received, so this is a connection that broke during the body - reset by peer, TLS error, or the 30-second client timeout expiring while streaming. The status code and content type are still returned alongside the error.
Solutions
- Retry the request - mid-body failures are usually transient; use short backoff
- Shrink the response (pagination, filters, Range) so the transfer finishes well inside 30 seconds
- Reproduce with curl --limit-rate or plain curl to see whether the server itself truncates the stream
Example fix
// before: one attempt
code, ct, text, err := util.HTTPRequest("GET", u, nil, "")
// after: retry on mid-body failure
for attempt := 0; attempt < 3; attempt++ {
code, ct, text, err = util.HTTPRequest("GET", u, nil, "")
if err == nil || !strings.Contains(err.Error(), "read body failed") {
break
}
time.Sleep(time.Duration(attempt+1) * time.Second)
} Defensive patterns
Strategy: retry
Try / catch
for attempt := 0; attempt < 3; attempt++ {
code, ct, text, err = util.HTTPRequest("GET", u, nil, "")
if err == nil || !strings.Contains(err.Error(), "read body failed") {
break
}
time.Sleep(time.Duration(attempt+1) * time.Second)
} Prevention
- Shrink responses (pagination, filters, Range) so transfers finish well inside the 30-second total budget
- Prefer stable wired networks for large fetches
- Treat repeated mid-body failures on one endpoint as a server-side truncation problem; verify with curl
When it happens
Trigger: Server closes the connection early (bad Content-Length, crashing handler); flaky network resets the stream; slow transfer of a near-cap body that cannot finish within the client's 30s total budget.
Common situations: Mobile or unstable links; overloaded origins that drop long responses; proxies cutting long transfers; responses whose declared length disagrees with actual bytes.
Related errors
- download failed:
- AI editor request timeout
- AI editor stream idle timeout
- authentication probe returned HTTP " + response.status
- boot progress request returned HTTP " + response.status
AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18).
Data as JSON: /api/errors/3b94c6bdf23f0bdc.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/httprequest.go:356
return 0, "", "", errors.New("nil response")
}
defer resp.Body.Close()
statusCode = resp.StatusCode
contentType = resp.Header.Get("Content-Type")
maxReadBytes := int64(maxHTTPRequestBytes)
if !isTextContentType(contentType) {
maxReadBytes = maxHTTPRequestFileBytes
}
// ContentLength 为 -1(chunked)时跳过大小预检,交由 LimitReader 兜底截断。
if resp.ContentLength > maxReadBytes {
return statusCode, contentType, "", errors.New("response too large")
}
respBody, rerr := io.ReadAll(io.LimitReader(resp.Body, maxReadBytes))
if rerr != nil {
return statusCode, contentType, "", errors.New("read body failed: " + rerr.Error())
}
// 二进制响应落盘,返回文件路径,供智能体按需进一步处理。
if !isTextContentType(contentType) {
importDir := filepath.Join(TempDir, "import")
if merr := os.MkdirAll(importDir, 0755); merr != nil {
return statusCode, contentType, "", errors.New("create import dir failed: " + merr.Error())
}
filename := extractFilename(rawURL, contentType)
filePath := filepath.Join(importDir, filename)
if werr := os.WriteFile(filePath, respBody, 0644); werr != nil {
return statusCode, contentType, "", errors.New("write file failed: " + werr.Error())
}
return statusCode, contentType, fmt.Sprintf("Saved to: %s (%d bytes)", filePath, len(respBody)), nil
}
return statusCode, contentType, truncateRunes(string(respBody), maxHTTPRequestChars), nil
}View on GitHub (pinned to afa823b6b4)