{"record":{"id":"c3f06bcafd9428a1","repo":"OpenNHP/opennhp","slug":"relay-server-d-fingerprint-s-has-no-server","errorCode":null,"errorMessage":"relay: server #%d (fingerprint %s) has no [[Servers.Instances]]","messagePattern":"relay: server #(.+?) \\(fingerprint (.+?)\\) has no \\[\\[Servers\\.Instances\\]\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"endpoints/relay/config.go","lineNumber":241,"sourceCode":"\t\tinstance int\n\t}\n\tseenAddr := make(map[string]addrOrigin)\n\tfor i := range cfg.Servers {\n\t\tc := &cfg.Servers[i]\n\t\tif c.PubKeyBase64 == \"\" {\n\t\t\treturn fmt.Errorf(\"relay: server #%d missing publicKeyBase64\", i)\n\t\t}\n\t\tfp, err := utils.PubKeyFingerprintFromBase64(c.PubKeyBase64)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"relay: server #%d publicKeyBase64 invalid: %w\", i, err)\n\t\t}\n\t\tif dup, ok := seenFP[fp]; ok {\n\t\t\treturn fmt.Errorf(\"relay: server #%d and #%d share the same publicKeyBase64 (fingerprint %s)\", dup, i, fp)\n\t\t}\n\t\tseenFP[fp] = i\n\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}","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/relay/config.go#L223-L259","documentation":"Each [[Servers]] entry must contain at least one [[Servers.Instances]] endpoint; normalize rejects a server with an empty Instances list, reporting the server index and its fingerprint. The relay needs concrete host:port endpoints to forward traffic to.","triggerScenarios":"A [[Servers]] block with publicKeyBase64 set is followed by no [[Servers.Instances]] sub-table (or it is misspelled, e.g. [[Server.Instances]]), so c.Instances has length 0 during normalize.","commonSituations":"Operator wrote [[Server.Instances]] or [Servers.Instances] (wrong bracket count / placement) so TOML does not attach it to the right server; deleted the only instance during cleanup; template rendering dropped the instance section; ordering mistake placing [[Servers.Instances]] before any [[Servers]].","solutions":["Add at least one [[Servers.Instances]] block with host and port after the corresponding [[Servers]] block","Check the header is exactly [[Servers.Instances]] and appears after its parent [[Servers]] entry","Verify with a TOML parser that Instances is non-empty for each server entry"],"exampleFix":"// before\n[[Servers]]\npubKeyBase64 = \"KEY_A\"\n// after\n[[Servers]]\npubKeyBase64 = \"KEY_A\"\n[[Servers.Instances]]\nhost = \"10.0.0.5\"\nport = 10161","handlingStrategy":"validation","validationCode":"for i, s := range cfg.Servers {\n\tif len(s.Instances) == 0 {\n\t\treturn fmt.Errorf(\"server #%d has no instances\", i)\n\t}\n}","typeGuard":"func hasInstances(s relay.Server) bool { return len(s.Instances) > 0 }","tryCatchPattern":"if err := cfg.Normalize(); err != nil {\n\tif strings.Contains(err.Error(), \"has no [[Servers.Instances]]\") {\n\t\treturn errors.New(\"each [[Servers]] needs at least one [[Servers.Instances]] block\")\n\t}\n\treturn err\n}","preventionTips":["Use the exact header [[Servers.Instances]] immediately after its [[Servers]] block","Validate the parsed TOML tree in CI to confirm instances attach to the right server","Never commit [[Servers]] blocks without an instance entry"],"tags":["relay","config","validation","instances"],"backgroundTag":"missing-required-config-field","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"}