OpenNHP/opennhp · error
cluster ( )
Error message
cluster %q (%s): %w
What it means
buildCluster wraps cfg.LoadBalance.Validate()'s error as "cluster %q (%s): %w" when the cluster's load-balance setting is invalid. The wrapped error names the unacceptable scheme, and this wrapper adds the cluster name and public key for identification.
Solutions
- Set loadBalance to one of the exact supported values (check the LoadBalance type/Validate in endpoints/agent)
- Check the wrapped error text for the offending value and correct the casing/spelling
- Omit the field to use the default scheme if one exists
- Add the accepted values to config documentation/templates to prevent recurrence
Example fix
// before [[clusters]] name = "nhp-server" publicKeyBase64 = "abc..." loadBalance = "least-connections" # unsupported // after [[clusters]] name = "nhp-server" publicKeyBase64 = "abc..." loadBalance = "roundRobin" # supported scheme
Defensive patterns
Strategy: validation
Validate before calling
var lb LoadBalance
if err := lb.UnmarshalText([]byte(cfg.LoadBalance)); err != nil {
return fmt.Errorf("cluster %q: bad loadBalance %q (allowed: %v)", cfg.Name, cfg.LoadBalance, lb.Allowed())
} Try / catch
cl, err := buildCluster(cfg)
if err != nil {
var le *LoadBalanceError
if errors.As(err, &le) { /* fix the scheme value */ }
} Prevention
- Use a typed enum with strict unmarshalling for load balance settings
- Document exact accepted values in config templates/examples
- Reject unknown values at config parse time, not cluster build time
When it happens
Trigger: A cluster config sets loadBalance (or similar field) to a string/enum outside the supported set (e.g. "round-robin" vs supported values like random/roundRobin/priority), so LoadBalance.Validate() returns an error during updateServerPeers.
Common situations: Typo in the load-balance mode value; using a scheme removed in a newer version; case-sensitivity mismatch ("Round-Robin" vs "roundRobin"); copying config from a different project with different scheme names.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- cluster : missing publicKeyBase64
- cluster ( ): no instances configured
- no private key configured; check etc/config.toml
- unknown remote provider
- unknown remote provider
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/19d8c1c2992750a0.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/agent/cluster.go:140
}
}
return nil
}
// buildCluster turns a parsed ClusterConfig into a runtime cluster.
// The returned cluster's representativePeer is NOT yet registered on a
// device — callers (updateServerPeers) are responsible for that, so
// they can also handle peer removal on reload.
func buildCluster(cfg *ClusterConfig) (*ServerCluster, error) {
if cfg.PubKeyBase64 == "" {
return nil, fmt.Errorf("cluster %q: missing publicKeyBase64", cfg.Name)
}
if len(cfg.Instances) == 0 {
return nil, fmt.Errorf("cluster %q (%s): no instances configured",
cfg.Name, cfg.PubKeyBase64)
}
if err := cfg.LoadBalance.Validate(); err != nil {
return nil, fmt.Errorf("cluster %q (%s): %w",
cfg.Name, cfg.PubKeyBase64, err)
}
sc := &ServerCluster{
PublicKeyBase64: cfg.PubKeyBase64,
Name: cfg.Name,
Sticky: cfg.StickyOrDefault(),
instances: make([]*ServerInstance, 0, len(cfg.Instances)),
}
for i, ic := range cfg.Instances {
host := ic.Host
ip := ic.Ip
if host == "" && ip == "" {
return nil, fmt.Errorf("cluster %q instance #%d: must set either Host or Ip",
cfg.Name, i)
}
if ic.Port <= 0 {View on GitHub (pinned to 6e04ca5ff0)