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

  1. Read the wrapped readErr in the message to identify the transport cause (unexpected EOF, connection reset, context deadline).
  2. Retry the request; transient body-read failures usually succeed on retry.
  3. Check proxy/load-balancer timeout settings between the plugin host and the kernel.
  4. Reduce response size (narrow the API query) or increase network timeouts.
  5. 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

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


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
					return

View on GitHub (pinned to 9f775e8a12)