OpenNHP/opennhp · error

: [[Servers]][ ] is nil

Error message

%s: [[Servers]][%d] is nil

What it means

During Normalize's per-cluster loop, a nil *ClusterConfig entry in the clusters slice is rejected. A nil entry carries no public key or address and cannot become a peer, so normalization aborts with the index of the offending entry.

Solutions

  1. Populate every element of the clusters slice before calling Normalize
  2. Filter out nil entries in the loader: append only successfully decoded *ClusterConfig values
  3. Fix the code path that created the slice so it grows with append instead of a fixed make(..., n)
  4. Ensure the TOML parser surfaces malformed [[Servers]] blocks as errors rather than nil entries

Example fix

// before
clusters := make([]*clusterconfig.ClusterConfig, len(raw))
normalize(clusters)
// after
var clusters []*clusterconfig.ClusterConfig
for _, r := range raw {
    if c := decode(r); c != nil {
        clusters = append(clusters, c)
    }
}
normalize(clusters)
Defensive patterns

Strategy: type-guard

Validate before calling

for i, c := range clusters {
    if c == nil { return fmt.Errorf("cluster %d is nil before Normalize", i) }
}
err := clusterconfig.Normalize(clusters, opts)

Type guard

func allClustersSet(cs []*clusterconfig.ClusterConfig) bool {
    for _, c := range cs { if c == nil { return false } }
    return len(cs) > 0
}

Try / catch

if err := clusterconfig.Normalize(clusters, opts); err != nil {
    if strings.Contains(err.Error(), "is nil") { log.Fatalf("loader produced nil cluster entry: %v", err) }
    return err
}

Prevention

When it happens

Trigger: Calling Normalize where clusters[i] == nil for some i — e.g. a slice pre-allocated with make([]*ClusterConfig, n) and never fully populated, or a loader appending nil for malformed TOML blocks.

Common situations: Programmatic construction of the cluster slice sized but not filled; a custom config loader that appends nil on decode errors instead of skipping; test harnesses building partial fixtures.

Related errors


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

Appendix: source

Thrown at nhp/common/clusterconfig/clusterconfig.go:144

//
// deprecate is a callback so callers control how the warning is
// surfaced — production code passes log.Warning; tests can capture
// invocations for assertions. A nil deprecate is treated as no-op.
func Normalize(clusters []*ClusterConfig, opts Options, deprecate func(string, ...any)) error {
	if deprecate == nil {
		deprecate = func(string, ...any) {}
	}
	label := opts.ConsumerLabel
	if label == "" {
		label = "cluster"
	}

	if len(clusters) == 0 {
		return fmt.Errorf("%s: no [[Servers]] configured", label)
	}
	for i, c := range clusters {
		if c == nil {
			return fmt.Errorf("%s: [[Servers]][%d] is nil", label, i)
		}
		if c.PubKeyBase64 == "" {
			return fmt.Errorf("%s: [[Servers]][%d] missing PubKeyBase64", label, i)
		}
		if opts.RequireName {
			if c.Name == "" {
				return fmt.Errorf("%s: [[Servers]][%d] (%s) missing Name — clusters are referenced from resource.toml by Name",
					label, i, c.PubKeyBase64)
			}
			if len(c.Name) > NameMaxLen {
				return fmt.Errorf("%s: [[Servers]][%d] Name %q exceeds %d chars",
					label, i, c.Name, NameMaxLen)
			}
			if !clusterNameRegex.MatchString(c.Name) {
				return fmt.Errorf("%s: [[Servers]][%d] Name %q invalid — allowed chars: [a-zA-Z0-9._-]",
					label, i, c.Name)
			}
		}

View on GitHub (pinned to 6e04ca5ff0)