{"record":{"id":"08deab20821d70a3","repo":"XTLS/Xray-core","slug":"duplicate-balancer-tag","errorCode":null,"errorMessage":"duplicate balancer tag","messagePattern":"duplicate balancer tag","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/router/router.go","lineNumber":139,"sourceCode":"}\n\nfunc (r *Router) ReloadRules(config *Config, shouldAppend bool) error {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\n\tif !shouldAppend {\n\t\tfor _, rule := range r.rules {\n\t\t\tif rule.Webhook != nil {\n\t\t\t\trule.Webhook.Close()\n\t\t\t}\n\t\t}\n\t\tr.balancers = make(map[string]*Balancer, len(config.BalancingRule))\n\t\tr.rules = make([]*Rule, 0, len(config.Rule))\n\t}\n\tfor _, rule := range config.BalancingRule {\n\t\t_, found := r.balancers[rule.Tag]\n\t\tif found {\n\t\t\treturn errors.New(\"duplicate balancer tag\")\n\t\t}\n\t\tbalancer, err := rule.Build(r.ohm, r.dispatcher)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tbalancer.InjectContext(r.ctx)\n\t\tr.balancers[rule.Tag] = balancer\n\t}\n\n\tstartIdx := len(r.rules)\n\tcloseNewWebhooks := func() {\n\t\tfor i := startIdx; i < len(r.rules); i++ {\n\t\t\tif r.rules[i].Webhook != nil {\n\t\t\t\tr.rules[i].Webhook.Close()\n\t\t\t}\n\t\t}\n\t\tr.rules = r.rules[:startIdx]\n\t}","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/app/router/router.go#L121-L157","documentation":"While (re)building balancers, two balancingRule entries declare the same tag. Balancers are keyed by tag in a map, so duplicates would silently overwrite each other; the loader rejects instead. Note the early return leaks already-built balancers' webhooks state, but config load fails anyway.","triggerScenarios":"ReloadRules/AddRule config containing two balancingRule blocks with identical tag values; append-mode reload re-adding an existing balancer tag.","commonSituations":"Copy-pasted balancer blocks in JSON; append reloads (shouldAppend=true) that repeat tags already registered.","solutions":["Give each balancingRule a unique tag","In append mode, only include new balancer tags not already loaded","Lint the config for duplicate tags before submit"],"exampleFix":"// before\n\"balancingRule\": [\n  { \"tag\": \"bal\", \"selector\": [\"a\"] },\n  { \"tag\": \"bal\", \"selector\": [\"b\"] }\n]\n\n// after\n\"balancingRule\": [\n  { \"tag\": \"bal-a\", \"selector\": [\"a\"] },\n  { \"tag\": \"bal-b\", \"selector\": [\"b\"] }\n]","handlingStrategy":"validation","validationCode":"seen := map[string]bool{}\nfor _, b := range cfg.BalancingRule {\n    if seen[b.Tag] { return fmt.Errorf(\"duplicate balancer tag %q\", b.Tag) }\n    seen[b.Tag] = true\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Deduplicate balancer tags in config linting before deploy","In append reloads, diff new tags against already-registered ones","Use distinct, meaningful tag names per selector group"],"tags":["routing","balancer","configuration","duplicate"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}