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
- Pin master and all nodes to the same release version; this is the most common cause and the fastest fix.
- curl the node's /panel/api/inbounds/list and compare the obj shape with what the master's RemoteInboundOption expects.
- Upgrade nodes one at a time and re-run the inbound-options fetch to confirm compatibility.
- 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
- Treat master and node releases as one deployment unit — upgrade together.
- Add a compatibility probe after node upgrades before enabling client-facing features.
- Log the node's reported version with every decode failure to speed diagnosis.
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
- decode web cert files: %w
- %s %s: HTTP %d: %q
- %s %s: %w
- %s %s: HTTP %d
- %s %s: %w (content-length %d, cap %d)
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/c24eb47b375ded1e.
Report an issue: GitHub.