MHSanaei/3x-ui · error
read body: %w
Error message
read body: %w
What it means
Returned by Remote.do when reading the response body fails for a reason OTHER than the size cap — i.e. a transport-level failure mid-body. The status was 200 and headers were fine, but the stream broke before completion. The wrapped error is the raw io/network error from the body reader.
Source
Thrown at internal/web/runtime/remote.go:281
// newlines in it can't garble or inject into the error/log output.
return nil, fmt.Errorf("%s %s: HTTP %d: %q", method, path, resp.StatusCode, msg)
}
return nil, fmt.Errorf("%s %s: HTTP %d", method, path, resp.StatusCode)
}
// Fast-fail on an honestly-declared oversize body; the LimitReader below is
// the real guard since Content-Length is untrusted, may be absent, or is -1
// under transparent decompression.
if resp.ContentLength > maxRemoteResponseBytes {
return nil, fmt.Errorf("%s %s: %w (content-length %d, cap %d)", method, path, errRemoteResponseTooLarge, resp.ContentLength, maxRemoteResponseBytes)
}
raw, err := readCappedBody(resp.Body, maxRemoteResponseBytes)
if err != nil {
if errors.Is(err, errRemoteResponseTooLarge) {
return nil, fmt.Errorf("%s %s: %w (cap %d bytes)", method, path, err, maxRemoteResponseBytes)
}
return nil, fmt.Errorf("read body: %w", err)
}
var env envelope
if err := json.Unmarshal(raw, &env); err != nil {
return nil, fmt.Errorf("decode envelope: %w", err)
}
if !env.Success {
return &env, &remoteAPIError{msg: env.Msg}
}
return &env, nil
}
func (r *Remote) resolveRemoteID(ctx context.Context, tag string) (int, error) {
if id, ok := r.cacheGetTag(tag); ok {
return id, nil
}
if err := r.refreshRemoteIDs(ctx); err != nil {
return 0, errView on GitHub (pinned to ad32144c42)
Solutions
- Retry the operation — mid-body failures are usually transient; the runtime marks the node dirty and ReconcileNode will converge state anyway.
- If it recurs on the same node, check the node's logs for a crash (OOM, panic) at that timestamp.
- Raise proxy read timeouts between master and node if large payloads consistently get cut.
- Inspect the wrapped cause: 'context deadline exceeded' means the RPC timeout is too small for the payload, not a network fault.
Example fix
// before: 10s RPC timeout, 20MB inbound list over slow link // err: read body: context deadline exceeded // after: raise the per-call timeout budget in the caller (context.WithTimeout) or reduce payload size ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
Defensive patterns
Strategy: retry
Type guard
func isMidBodyFailure(err error) bool {
return err != nil && strings.HasPrefix(err.Error(), "read body:") && !isResponseTooLarge(err)
} Try / catch
err := withRetry(3, func() error {
e, err := r.do(ctx, method, path, body)
env = e
return err
}) // only retry when isMidBodyFailure(err) Prevention
- Set proxy read/idle timeouts between master and node above the largest expected transfer time.
- Watch node memory: OOM-kills during serialization surface here as connection resets.
- Size RPC context timeouts to the payload, not a fixed small constant.
When it happens
Trigger: Connection reset by the node or an intermediate proxy while streaming a large response; node process killed mid-response; TLS renegotiation failure; idle-timeout on a proxy cutting long transfers; context cancelled during the read.
Common situations: Flaky network path between master and node; proxy idle/response timeout shorter than the time to stream a big inbound list; node OOM-killed during serialization of a large payload.
Related errors
- %s %s: %w
- node port must be 1-65535
- HTTP %d from remote panel
- remote response exceeds size limit
- outbound subscription response body exceeds size limit
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/63be470db6dfb876.
Report an issue: GitHub.