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

  1. Retry the request - mid-body failures are usually transient; use short backoff
  2. Shrink the response (pagination, filters, Range) so the transfer finishes well inside 30 seconds
  3. 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

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


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)