MHSanaei/3x-ui · error · errRemoteResponseTooLarge
%s %s: %w (cap %d bytes)
Error message
%s %s: %w (cap %d bytes)
What it means
Returned by Remote.do when the streamed body itself exceeds maxRemoteResponseBytes (readCappedBody surfaced errRemoteResponseTooLarge). This is the real guard for the case Content-Length lied, was absent (-1 under transparent decompression), or the body was zstd-compressed on the wire and inflated past the cap during reading.
Source
Thrown at internal/web/runtime/remote.go:279
if msg := bytes.TrimSpace(snippet); len(msg) > 0 {
// %q quotes/escapes the untrusted node body so control characters or
// 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
}View on GitHub (pinned to ad32144c42)
Solutions
- Same triage as the declared-length variant: fetch the endpoint manually and measure the true decompressed size.
- Reduce per-node payload (fewer inbounds/clients per node) if the data is legitimate.
- Raising maxRemoteResponseBytes is a deliberate capacity decision — measure first, then change the constant in one place in remote.go.
- Confirm no proxy is re-chunking responses and defeating the fast-fail path.
Example fix
// before: 200 OK, chunked, 30MB decompressed inbound list // err: GET panel/api/inbounds/list: remote response too large (cap 8388608 bytes) // after: distribute inbounds across nodes, or measure and raise the cap // const maxRemoteResponseBytes = 64 << 20
Defensive patterns
Strategy: validation
Type guard
func isResponseTooLarge(err error) bool {
return err != nil && strings.Contains(err.Error(), "remote response too large")
} Try / catch
if err := fetchList(ctx); err != nil {
if isResponseTooLarge(err) {
return nil, fmt.Errorf("node %d payload exceeds cap: shard inbounds or raise maxRemoteResponseBytes", nodeID)
}
return nil, err
} Prevention
- Remember the cap applies to DECOMPRESSED size — compressed transfers can still exceed it.
- Re-measure payload sizes when enabling zstd between master and node.
- Change maxRemoteResponseBytes only after measuring; document the new budget.
When it happens
Trigger: Node responds 200 with chunked encoding or Content-Encoding: zstd whose decompressed size exceeds the cap; declared length under the cap but actual bytes over it; a compromised or buggy node streaming unbounded data.
Common situations: Large inbound list served with transparent compression so the wire length is small but the decoded JSON is huge; proxy stripping Content-Length; node bug looping while serializing clients.
Related errors
- %s %s: %w (content-length %d, cap %d)
- %s %s: %w
- %s %s: HTTP %d: %q
- %s %s: HTTP %d
- Request failed with status ${status}
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/7055eb4e79ce2273.
Report an issue: GitHub.