XTLS/Xray-core · warning
unsupported GetPrincipleTarget
Error message
unsupported GetPrincipleTarget
What it means
Router.GetPrincipleTarget was called with a valid balancer tag, but the balancer's strategy does not implement the BalancingPrincipleTarget interface (the optional GetPrincipleTarget(candidates) method). Only strategies that expose their decision rationale — such as built-in leastload/leastping styles that implement it — can answer; random or custom strategies cannot, so the router returns this unsupported-capability error.
Source
Thrown at app/router/balancing.go:147
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
func (r *Router) GetOverrideTarget(tag string) (string, error) {
if b, ok := r.balancers[tag]; ok {
return b.override.Get(), nil
}View on GitHub (pinned to 7d214f8b09)
Solutions
- Switch the balancer's strategy to one implementing principle-target reporting (e.g. leastPing/leastLoad) if the observation feature is needed.
- Keep random balancers but have tooling expect and skip this error (capability probe pattern).
- No forwarding impact — verify data path separately before spending time here.
Example fix
// before
"balancers": [ { "tag": "bal", "selector": [ "node" ], "strategy": { "type": "random" } } ]
// after
"balancers": [ { "tag": "bal", "selector": [ "node" ], "strategy": { "type": "leastPing" } } ] Defensive patterns
Strategy: type-guard
Validate before calling
null // runtime capability depends on the configured strategy type
Type guard
func strategyReportsPrincipleTarget(strategyType string) bool {
switch strategyType {
case "leastPing", "leastLoad", "": return true // defaults implement it
default: return false // e.g. random, custom
}
} Try / catch
if _, err := router.GetPrincipleTarget(tag); err != nil {
if strings.Contains(err.Error(), "unsupported GetPrincipleTarget") {
return nil, nil // capability gap, not an error condition for callers
}
return nil, err
} Prevention
- Map which strategies implement principle-target before wiring dashboards
- Switch to leastPing/leastLoad when 'current pick' visibility is required
- Design panels to hide the metric gracefully for random/custom balancers
When it happens
Trigger: Calling the principle-target API for a balancer configured with "strategy" whose type is random (or a custom strategy) — the type assertion b.strategy.(BalancingPrincipleTarget) fails and the error is returned before any candidate work.
Common situations: Panels/tools that display 'current best node' querying every balancer, including ones using the random strategy; users switching a balancer from leastping to random and seeing the API query start failing while forwarding still works fine.
Related errors
- unable to select outbounds
- 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/2799411805db527f.
Report an issue: GitHub.