OpenNHP/opennhp · error

relay: server # (fingerprint ) has no [[Servers.Instances]]

Error message

relay: server #%d (fingerprint %s) has no [[Servers.Instances]]

What it means

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.

Solutions

  1. Add at least one [[Servers.Instances]] block with host and port after the corresponding [[Servers]] block
  2. Check the header is exactly [[Servers.Instances]] and appears after its parent [[Servers]] entry
  3. Verify with a TOML parser that Instances is non-empty for each server entry

Example fix

// before
[[Servers]]
pubKeyBase64 = "KEY_A"
// after
[[Servers]]
pubKeyBase64 = "KEY_A"
[[Servers.Instances]]
host = "10.0.0.5"
port = 10161
Defensive patterns

Strategy: validation

Validate before calling

for i, s := range cfg.Servers {
	if len(s.Instances) == 0 {
		return fmt.Errorf("server #%d has no instances", i)
	}
}

Type guard

func hasInstances(s relay.Server) bool { return len(s.Instances) > 0 }

Try / catch

if err := cfg.Normalize(); err != nil {
	if strings.Contains(err.Error(), "has no [[Servers.Instances]]") {
		return errors.New("each [[Servers]] needs at least one [[Servers.Instances]] block")
	}
	return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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]].

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/c3f06bcafd9428a1. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/relay/config.go:241

		instance int
	}
	seenAddr := make(map[string]addrOrigin)
	for i := range cfg.Servers {
		c := &cfg.Servers[i]
		if c.PubKeyBase64 == "" {
			return fmt.Errorf("relay: server #%d missing publicKeyBase64", i)
		}
		fp, err := utils.PubKeyFingerprintFromBase64(c.PubKeyBase64)
		if err != nil {
			return fmt.Errorf("relay: server #%d publicKeyBase64 invalid: %w", i, err)
		}
		if dup, ok := seenFP[fp]; ok {
			return fmt.Errorf("relay: server #%d and #%d share the same publicKeyBase64 (fingerprint %s)", dup, i, fp)
		}
		seenFP[fp] = i

		if len(c.Instances) == 0 {
			return fmt.Errorf("relay: server #%d (fingerprint %s) has no [[Servers.Instances]]", i, fp)
		}
		for j := range c.Instances {
			inst := &c.Instances[j]
			if inst.Host == "" {
				return fmt.Errorf("relay: server #%d instance #%d missing host", i, j)
			}
			if inst.Port <= 0 {
				return fmt.Errorf("relay: server #%d instance #%d missing or invalid port", i, j)
			}
			addr := fmt.Sprintf("%s:%d", inst.Host, inst.Port)
			// Scope to this server's pubkey: same identity reusing an
			// address is the copy-paste error we reject; a sibling
			// identity on the same address is allowed (see seenAddr docs).
			addrKey := fp + "@" + addr
			if dup, ok := seenAddr[addrKey]; ok {
				return fmt.Errorf("relay: server #%d instance #%d address %s already claimed by server #%d instance #%d under the same publicKeyBase64 (fingerprint %s)",
					i, j, addr, dup.server, dup.instance, fp)
			}

View on GitHub (pinned to 6e04ca5ff0)