MHSanaei/3x-ui · error
decode envelope: %w
Error message
decode envelope: %w
What it means
Returned by Remote.do when the 200 response body is not valid JSON for the wire envelope ({success, msg, obj}). The node answered OK at the HTTP layer but the payload is not the expected protocol — most commonly an HTML error/login page, a plaintext proxy message, or truncated output. It indicates protocol mismatch, not a node-side business error.
Source
Thrown at internal/web/runtime/remote.go:286
// 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, err
}
if id, ok := r.cacheGetTag(tag); ok {
return id, nil
}
return 0, fmt.Errorf("remote inbound with tag %q not found on node %s", tag, r.node.Name)View on GitHub (pinned to ad32144c42)
Solutions
- Manually reproduce: curl -H 'Authorization: ...' https://node/panel/api/inbounds/list and look at the raw body — if it is HTML, the request never reached the API.
- Re-save the node's credential on the master so the auth header the runtime sends is accepted by the node's middleware.
- Fix the node address to point at the panel port, not the subscription/other listener.
- Align master and node versions if the envelope shape changed between releases.
Example fix
// before: wrong port (subscription server) configured as node address // err: decode envelope: invalid character '<' looking for beginning of value // after: point the node record at the panel's HTTPS port serving /panel/api/*
Defensive patterns
Strategy: validation
Validate before calling
// Smoke-test the wire contract once per node at registration
env, err := r.do(ctx, http.MethodGet, "panel/api/inbounds/list", nil)
if err != nil { return err } // a decode-envelope failure here means auth/path is wrong Type guard
func isEnvelopeDecodeError(err error) bool {
return err != nil && strings.HasPrefix(err.Error(), "decode envelope:")
} Try / catch
if err := r.do(ctx, method, path, body); err != nil {
if isEnvelopeDecodeError(err) {
return fmt.Errorf("node %s speaks wrong protocol (auth redirect or wrong port): %w", r.node.Name, err)
}
return err
} Prevention
- After registering a node, run one probe RPC from the master before relying on it.
- Ensure the node API token is accepted — an auth redirect that returns 200 HTML is the classic cause.
- Never front the node API with an SPA-serving proxy that masks errors as 200 HTML.
When it happens
Trigger: The master's request hit the node's web LOGIN page (session middleware redirected to /login and returned 200 HTML) because the RPC auth header/token was missing or rejected; a reverse proxy served a 200 status page; the body was truncated by an intermediary; node version speaks a different envelope.
Common situations: Node API token expired/rotated so auth middleware silently redirects; proxy rewrites the path and serves an SPA index.html with 200; master pointed at the node's subscription port instead of the panel port.
Related errors
- %s %s: HTTP %d: %q
- %s %s: HTTP %d
- decode inbound list: %w
- decode web cert files: %w
- password can not be empty
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/81a85a8cda6a4b22.
Report an issue: GitHub.