siyuan-note/siyuan · error

read body failed: %s

Error message

read body failed: %s

What it means

Returned by HTTPRequest when io.ReadAll on the LimitReader-bounded body fails. At this point headers (status, Content-Type, Content-Length) were already read successfully; the failure is mid-stream while reading the response body. The underlying read error is appended for diagnostics.

Source

Thrown at kernel/util/httprequest.go:109

		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 251596fc0d)

Solutions

  1. Retry the request once — mid-stream read failures are often transient.
  2. If recurring, reduce the requested payload (paginate) so the body completes before any connection idle timeout.
  3. Inspect the appended I/O error text to distinguish a reset ('connection reset') from a timeout ('i/o timeout').

Example fix

// before
_, _, _, err := util.HTTPRequest("GET", rawURL, nil, "")

// after
var err error
for attempt := 0; attempt < 2; attempt++ {
    _, _, _, err = util.HTTPRequest("GET", rawURL, nil, "")
    if err == nil || !strings.Contains(err.Error(), "read body failed") {
        break
    }
    time.Sleep(500 * time.Millisecond)
}
Defensive patterns

Strategy: retry

Try / catch

var err error
for attempt := 0; attempt < 2; attempt++ {
    _, _, _, err = util.HTTPRequest(method, rawURL, headers, body)
    if err == nil || !strings.Contains(err.Error(), "read body failed") { break }
    time.Sleep(500 * time.Millisecond)
}

Prevention

When it happens

Trigger: After receiving response headers, the connection drops or stalls while the body is still being read; the server closes the connection early; the LimitReader hits a transient I/O error. Note: because LimitReader is used, EOF after the cap is not an error — this fires only on an actual read failure.

Common situations: An unstable network link, a server that times out mid-response, a proxy that closes idle connections, or a server streaming a body larger than the cap and the connection being reset before truncation completes.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/3b94c6bdf23f0bdc. Report an issue: GitHub.