{"record":{"id":"b1e196343e726721","repo":"juanfont/headscale","slug":"backfilling-ips-w-b1e196","errorCode":null,"errorMessage":"backfilling IPs: %w","messagePattern":"backfilling IPs: %w","errorType":"exception","errorClass":"errIPAllocatorNil","httpStatus":null,"severity":"error","filePath":"hscontrol/db/ip.go","lineNumber":314,"sourceCode":"\n// BackfillNodeIPs will take a database transaction, and\n// iterate through all of the current nodes ([types.Node]) in headscale\n// and ensure it has IP addresses according to the current\n// configuration.\n// This means that if both IPv4 and IPv6 is set in the\n// config, and some nodes are missing that type of IP,\n// it will be added.\n// If a prefix type has been removed (IPv4 or IPv6), it\n// will remove the IPs in that family from the node.\nfunc (db *HSDatabase) BackfillNodeIPs(i *IPAllocator) ([]string, error) {\n\tvar (\n\t\terr error\n\t\tret []string\n\t)\n\n\terr = db.Write(func(tx *gorm.DB) error {\n\t\tif i == nil {\n\t\t\treturn fmt.Errorf(\"backfilling IPs: %w\", errIPAllocatorNil)\n\t\t}\n\n\t\tlog.Trace().Caller().Msgf(\"starting to backfill IPs\")\n\n\t\tnodes, err := ListNodes(tx)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"listing nodes to backfill IPs: %w\", err)\n\t\t}\n\n\t\tfor _, node := range nodes {\n\t\t\tlog.Trace().Caller().EmbedObject(node).Msg(\"ip backfill check started because node found in database\")\n\n\t\t\tchanged := false\n\t\t\t// IPv4 prefix is set, but node ip is missing, alloc\n\t\t\tif i.prefix4 != nil && node.IPv4 == nil {\n\t\t\t\tret4, err := i.allocateNext(&i.prev4, i.prefix4)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"allocating IPv4 for node(%d): %w\", node.ID, err)","sourceCodeStart":296,"sourceCodeEnd":332,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/ip.go#L296-L332","documentation":"BackfillNodeIPs reconciles node IPs against the configured prefixes at startup. It refuses to run when the *IPAllocator argument is nil (errIPAllocatorNil), because there is no prefix configuration to backfill against. The guard fires before any database work, so no partial writes occur.","triggerScenarios":"Calling db.BackfillNodeIPs(nil), typically because the IPAllocator failed to construct (invalid or absent ipv4/ipv6 prefix in config) and the caller propagated a nil pointer instead of aborting.","commonSituations":"Removing or corrupting prefixes in config after previously running with them; upgrading headscale where the allocator construction path changed; prefix strings that fail netip.ParsePrefix so the caller silently keeps a nil allocator.","solutions":["Fix the prefix configuration (valid CIDRs for prefixes.v4 / prefixes.v6) so the IPAllocator is constructed","Find the caller that passes the allocator and make it return the construction error instead of continuing with nil","Fail startup on allocator construction error rather than calling backfill with nil"],"exampleFix":"// before\nalloc, _ := ipalloc.New(cfg.Prefix4, cfg.Prefix6) // error ignored, alloc may be nil\ndb.BackfillNodeIPs(alloc)\n\n// after\nalloc, err := ipalloc.New(cfg.Prefix4, cfg.Prefix6)\nif err != nil {\n\treturn fmt.Errorf(\"constructing IP allocator: %w\", err)\n}\ndb.BackfillNodeIPs(alloc)","handlingStrategy":"validation","validationCode":"if ipAlloc == nil {\n\treturn fmt.Errorf(\"cannot backfill IPs: IP allocator is nil (check prefix configuration)\")\n}\nchanges, err := db.BackfillNodeIPs(ipAlloc)","typeGuard":"func hasAllocator(i *db.IPAllocator) bool { return i != nil }","tryCatchPattern":null,"preventionTips":["Treat IPAllocator construction failure as fatal at startup","Never ignore the error returned when building the allocator","Add a config-validation step that rejects empty/invalid prefixes before boot"],"tags":["go","database","ip-allocation","configuration","nil-guard"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}