siyuan-note/siyuan · error
failed to read response body
Error message
failed to read response body: %w
What it means
The plugin API client received an HTTP response but io.ReadAll(resp.Body) failed while draining the response body. The client wraps the underlying read error (%w) so the transport-level cause (connection reset, premature EOF, timeout mid-body) is preserved. It means the request was sent and headers arrived, but the body could not be fully read.
Solutions
- Read the wrapped readErr in the message to identify the transport cause (unexpected EOF, connection reset, context deadline).
- Retry the request; transient body-read failures usually succeed on retry.
- Check proxy/load-balancer timeout settings between the plugin host and the kernel.
- Reduce response size (narrow the API query) or increase network timeouts.
- Verify kernel logs for panics or connection resets at the same timestamp.
Example fix
// before
const data = await client.request('/api/query/sql', { stmt: 'SELECT * FROM blocks' });
// after
try {
const data = await client.request('/api/query/sql', { stmt: 'SELECT * FROM blocks' });
} catch (e) {
if (String(e).includes('failed to read response body')) {
// retry once or fall back to a paginated query
const data = await client.request('/api/query/sql', { stmt: 'SELECT * FROM blocks LIMIT 1000' });
}
} Defensive patterns
Strategy: retry
Validate before calling
// no pre-call check possible; validate response handling instead
if (!navigator.onLine) { await waitForNetwork(); } Try / catch
try {
const data = await client.request(path, payload);
} catch (e) {
if (String(e).includes('failed to read response body')) {
data = await withRetry(() => client.request(path, payload), 2);
} else { throw e; }
} Prevention
- Implement idempotent retry with backoff for kernel API calls
- Keep payloads/responses modest in size or paginate large queries
- Check proxy and load-balancer idle timeouts in deployment configs
When it happens
Trigger: Calling siyuan.client.request/fetch from a plugin when the kernel closes the connection mid-transfer, the server times out while streaming the body, a proxy truncates the response, or the network drops between headers and body.
Common situations: Large kernel API responses (e.g. export or SQL query results) over flaky networks; reverse proxies with short idle timeouts; container networks dropping keepalive connections; mobile/unstable connections.
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
- authentication probe returned HTTP " + response.status
- boot progress request returned HTTP " + response.status
- discover OIDC provider failed
- download custom emoji failed
- download custom emoji failed with status
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/610283b1f000417a.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/plugin/api_client.go:141
}
r.SetHeader(model.XAuthTokenKey, p.token)
if bodyString != nil {
r.SetBody(*bodyString)
} else if bodyBytes != nil {
r.SetBody(*bodyBytes)
}
resp, sendErr := r.Send(method, targetURL)
if sendErr != nil {
err = sendErr
return
}
defer resp.Body.Close()
body, readErr := io.ReadAll(resp.Body)
if readErr != nil {
err = fmt.Errorf("failed to read response body: %w", readErr)
return
}
responseHeader := map[string]string{}
for k, vs := range resp.Header {
responseHeader[k] = strings.Join(vs, ", ")
}
runErr := p.worker.Run(func(rt *goja.Runtime) (result any, err error) {
response := rt.NewObject()
lo.Must0(response.Set("url", rt.ToValue(path)))
lo.Must0(response.Set("ok", rt.ToValue(resp.StatusCode >= 200 && resp.StatusCode < 300)))
lo.Must0(response.Set("status", rt.ToValue(resp.StatusCode)))
lo.Must0(response.Set("statusText", rt.ToValue(resp.Status)))
lo.Must0(response.Set("headers", rt.ToValue(responseHeader)))
lo.Must0(ObjectSetDataMethods(p, rt, response, body))
result = response
returnView on GitHub (pinned to 9f775e8a12)