{"record":{"id":"8c54d316e4767145","repo":"OpenNHP/opennhp","slug":"relay-server-d-instance-d-address-s-already","errorCode":null,"errorMessage":"relay: server #%d instance #%d address %s already claimed by server #%d instance #%d under the same publicKeyBase64 (fingerprint %s)","messagePattern":"relay: server #(.+?) instance #(.+?) address (.+?) already claimed by server #(.+?) instance #(.+?) under the same publicKeyBase64 \\(fingerprint (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"endpoints/relay/config.go","lineNumber":257,"sourceCode":"\n\t\tif len(c.Instances) == 0 {\n\t\t\treturn fmt.Errorf(\"relay: server #%d (fingerprint %s) has no [[Servers.Instances]]\", i, fp)\n\t\t}\n\t\tfor j := range c.Instances {\n\t\t\tinst := &c.Instances[j]\n\t\t\tif inst.Host == \"\" {\n\t\t\t\treturn fmt.Errorf(\"relay: server #%d instance #%d missing host\", i, j)\n\t\t\t}\n\t\t\tif inst.Port <= 0 {\n\t\t\t\treturn fmt.Errorf(\"relay: server #%d instance #%d missing or invalid port\", i, j)\n\t\t\t}\n\t\t\taddr := fmt.Sprintf(\"%s:%d\", inst.Host, inst.Port)\n\t\t\t// Scope to this server's pubkey: same identity reusing an\n\t\t\t// address is the copy-paste error we reject; a sibling\n\t\t\t// identity on the same address is allowed (see seenAddr docs).\n\t\t\taddrKey := fp + \"@\" + addr\n\t\t\tif dup, ok := seenAddr[addrKey]; ok {\n\t\t\t\treturn fmt.Errorf(\"relay: server #%d instance #%d address %s already claimed by server #%d instance #%d under the same publicKeyBase64 (fingerprint %s)\",\n\t\t\t\t\ti, j, addr, dup.server, dup.instance, fp)\n\t\t\t}\n\t\t\tseenAddr[addrKey] = addrOrigin{server: i, instance: j}\n\t\t\tif inst.Weight <= 0 {\n\t\t\t\tinst.Weight = 1\n\t\t\t}\n\t\t}\n\t\tswitch c.LoadBalance {\n\t\tcase \"\":\n\t\t\tc.LoadBalance = LBWeightedRandom\n\t\tcase LBRandom, LBWeightedRandom, LBRoundRobin:\n\t\t\t// known scheme, keep as-is\n\t\tdefault:\n\t\t\t// Typos like \"weighted_random\" or \"roundrobin\" are harmless in\n\t\t\t// phase 1 (the value is unused with a single instance) but\n\t\t\t// would silently degrade phase-2 load balancing to whatever\n\t\t\t// the default policy is. Reject at load time so the operator\n\t\t\t// hears about it now, not after a later upgrade.","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/relay/config.go#L239-L275","documentation":"Within a single server identity (same public key fingerprint), each instance address must be unique; normalize rejects an address already claimed by an earlier instance of the same server. Duplicates under one pubkey are treated as the classic copy-paste mistake, while different server identities on the same address are intentionally allowed. The error reports both conflicting server/instance indices and the address.","triggerScenarios":"Two [[Servers.Instances]] blocks under the SAME [[Servers]] pubkey resolve to the same host:port, so the fp+\"@\"+addr key already exists in seenAddr during normalize (LoadConfig startup or direct test invocation).","commonSituations":"Copying an [[Servers.Instances]] block to add a second instance and forgetting to change host or port; load-balancing intent expressed by duplicating the same endpoint instead of listing distinct instances; template loops rendering identical entries.","solutions":["Change the duplicate instance's host and/or port to the actual distinct upstream endpoint, or delete the redundant block","If multiple entries are meant to load-balance one service, list the real distinct host:port of each backend instance","Grep the config under the affected [[Servers]] block for repeated host/port pairs to find the duplicate before deploying"],"exampleFix":"// before\n[[Servers.Instances]]\nhost = \"10.0.0.5\"\nport = 10161\n[[Servers.Instances]]\nhost = \"10.0.0.5\"\nport = 10161\n// after\n[[Servers.Instances]]\nhost = \"10.0.0.5\"\nport = 10161\n[[Servers.Instances]]\nhost = \"10.0.0.6\"\nport = 10161","handlingStrategy":"validation","validationCode":"type key struct{ fp, addr string }\nseen := map[key]bool{}\nfor _, s := range cfg.Servers {\n\tfp := fingerprint(s.PubKeyBase64)\n\tfor _, inst := range s.Instances {\n\t\taddr := fmt.Sprintf(\"%s:%d\", inst.Host, inst.Port)\n\t\tk := key{fp, addr}\n\t\tif seen[k] {\n\t\t\treturn fmt.Errorf(\"duplicate instance address %s for same pubkey\", addr)\n\t\t}\n\t\tseen[k] = true\n\t}\n}","typeGuard":null,"tryCatchPattern":"if err := cfg.Normalize(); err != nil {\n\tif strings.Contains(err.Error(), \"already claimed by server\") {\n\t\treturn fmt.Errorf(\"remove or change the duplicated [[Servers.Instances]] entry: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["When adding instances, change host AND port, not just one","Generate instance lists programmatically from distinct backend endpoints rather than copy-paste","Add a CI check that fails on duplicate fp@addr pairs in relay configs"],"tags":["relay","config","duplicate","address"],"backgroundTag":"conflicting-config-options","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}