XTLS/Xray-core · warning

unable to select outbounds

Error message

unable to select outbounds

What it means

Router.GetPrincipleTarget failed to gather candidates: it calls b.SelectOutbounds() (which resolves the balancer's selectors through the outbound manager's HandlerSelector) and that returned an error, wrapped with this message. This is an API-surface error used by the stats/ observation layer to report which targets a balancer currently picks; it indicates the selector resolution layer is broken, not that balancing itself failed.

Source

Thrown at app/router/balancing.go:143

}

// SelectOutbounds select outbounds with selectors of the Balancer
func (b *Balancer) SelectOutbounds() ([]string, error) {
	hs, ok := b.ohm.(outbound.HandlerSelector)
	if !ok {
		return nil, errors.New("outbound.Manager is not a HandlerSelector")
	}
	tags := hs.Select(b.selectors)
	return tags, nil
}

// GetPrincipleTarget implements routing.BalancerPrincipleTarget
func (r *Router) GetPrincipleTarget(tag string) ([]string, error) {
	if b, ok := r.balancers[tag]; ok {
		if s, ok := b.strategy.(BalancingPrincipleTarget); ok {
			candidates, err := b.SelectOutbounds()
			if err != nil {
				return nil, errors.New("unable to select outbounds").Base(err)
			}
			return s.GetPrincipleTarget(candidates), nil
		}
		return nil, errors.New("unsupported GetPrincipleTarget")
	}
	return nil, errors.New("cannot find tag")
}

// SetOverrideTarget implements routing.BalancerOverrider
func (r *Router) SetOverrideTarget(tag, target string) error {
	if b, ok := r.balancers[tag]; ok {
		b.override.Put(target)
		return nil
	}
	return errors.New("cannot find tag")
}

// GetOverrideTarget implements routing.BalancerOverrider

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Confirm the outbound manager in use supports selection (feature outbound.Manager with HandlerSelector); custom/embedded managers must implement it for this API.
  2. Fix selector expressions so they match existing outbound tags.
  3. If the observation query is optional, tolerate/skip the error rather than treating it as a connectivity failure.
Defensive patterns

Strategy: type-guard

Validate before calling

// Capability probe before calling the API
if _, ok := router.(routing.BalancerPrincipleTarget); !ok { skipPrincipleTargetQuery() }

Type guard

func supportsPrincipleTarget(r *router.Router, balancerTag string) bool {
    b, ok := r.Balancer(balancerTag)
    if !ok { return false }
    _, ok = b.Strategy().(router.BalancingPrincipleTarget)
    return ok
}

Try / catch

if _, err := router.GetPrincipleTarget(tag); err != nil {
    if strings.Contains(err.Error(), "unable to select outbounds") {
        log.Warnf("observation degraded for balancer %s: %v", tag, err) // data path unaffected
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Invoking the balancer principle-target query (e.g. via router API/command) for a balancer whose underlying outbound manager does not implement HandlerSelector, or whose Select() call errors — commonly the manager was constructed without selector support or outbound tags referenced by selectors disappeared.

Common situations: Management/observation tooling (api queries like QueryStats/ GetPrincipleTarget-based panels) hitting a router whose outbound manager lacks selector capability; configs where the balancer selector references non-existent tags after renames. The data path usually still works; only the query fails.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/f1e60f4e3c560005. Report an issue: GitHub.