MHSanaei/3x-ui · error

decode inbound list: %w

Error message

decode inbound list: %w

What it means

Returned by Remote.ListInboundOptions when the node's panel/api/inbounds/list succeeded at the HTTP/envelope level but env.Obj does not decode into []RemoteInboundOption. The node returned 200 with a success envelope whose obj payload has a different shape than the master expects — a wire-format mismatch between builds.

Source

Thrown at internal/web/runtime/remote.go:377

		return nil, err
	}
	r.mu.RLock()
	defer r.mu.RUnlock()
	tags := make([]string, 0, len(r.remoteIDByTag))
	for tag := range r.remoteIDByTag {
		tags = append(tags, tag)
	}
	return tags, nil
}

func (r *Remote) ListInboundOptions(ctx context.Context) ([]RemoteInboundOption, error) {
	env, err := r.do(ctx, http.MethodGet, "panel/api/inbounds/list", nil)
	if err != nil {
		return nil, err
	}
	var list []RemoteInboundOption
	if err := json.Unmarshal(env.Obj, &list); err != nil {
		return nil, fmt.Errorf("decode inbound list: %w", err)
	}
	return list, nil
}

func (r *Remote) refreshRemoteIDs(ctx context.Context) error {
	env, err := r.do(ctx, http.MethodGet, "panel/api/inbounds/list", nil)
	if err != nil {
		return err
	}
	var list []struct {
		Id  int    `json:"id"`
		Tag string `json:"tag"`
	}
	if err := json.Unmarshal(env.Obj, &list); err != nil {
		return fmt.Errorf("decode inbound list: %w", err)
	}
	next := make(map[string]int, len(list))
	for _, ib := range list {

View on GitHub (pinned to ad32144c42)

Solutions

  1. Pin master and all nodes to the same release version; this is the most common cause and the fastest fix.
  2. curl the node's /panel/api/inbounds/list and compare the obj shape with what the master's RemoteInboundOption expects.
  3. Upgrade nodes one at a time and re-run the inbound-options fetch to confirm compatibility.
  4. Report the field-level mismatch if both sides are on the latest release (regression).

Example fix

// before: master v3-new, node v3-old with different list shape
// err: decode inbound list: json: cannot unmarshal object into Go struct field .obj of type []runtime.RemoteInboundOption

// after: upgrade node to the same release -> list decodes
Defensive patterns

Strategy: validation

Validate before calling

// Version-gate the feature before decoding
if !nodeVersionAtLeast(n, minListOptionsVersion) {
    return nil, fmt.Errorf("node %s too old for inbound options; upgrade it", n.Name)
}

Type guard

func isListDecodeError(err error) bool {
    return err != nil && strings.HasPrefix(err.Error(), "decode inbound list:")
}

Try / catch

opts, err := remote.ListInboundOptions(ctx)
if err != nil {
    if isListDecodeError(err) {
        return nil, fmt.Errorf("version mismatch with node %s: align releases and retry", r.node.Name)
    }
    return nil, err
}

Prevention

When it happens

Trigger: Calling ListInboundOptions against a node running an older/newer release whose inbound list JSON differs (renamed fields, nulls where structs expected, obj being an object instead of an array); a node returning a success envelope with obj as a string error placeholder.

Common situations: Master upgraded but nodes left on the previous release (or vice versa); mixed-version chained topology; a fork/custom node build with modified API responses.

Related errors


AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15). Data as JSON: /api/errors/c24eb47b375ded1e. Report an issue: GitHub.