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.BalancerOverriderView on GitHub (pinned to 7d214f8b09)
Solutions
- Confirm the outbound manager in use supports selection (feature outbound.Manager with HandlerSelector); custom/embedded managers must implement it for this API.
- Fix selector expressions so they match existing outbound tags.
- 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
- Probe capability before calling observation APIs per balancer
- Keep selector expressions in sync with existing outbound tags
- Treat these query errors as observability-only; verify forwarding separately
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
- unsupported GetPrincipleTarget
- cannot find tag
- balancing strategy returns empty tag
- can't get inbound proxy from handler.
- proxy is not a UserManager
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/f1e60f4e3c560005.
Report an issue: GitHub.