{"record":{"id":"cfd6d0f5519482cf","repo":"juanfont/headscale","slug":"reading-ipv6-addresses-from-database-w","errorCode":null,"errorMessage":"reading IPv6 addresses from database: %w","messagePattern":"reading IPv6 addresses from database: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/db/ip.go","lineNumber":86,"sourceCode":"\n\tvar (\n\t\tv4s []sql.NullString\n\t\tv6s []sql.NullString\n\t)\n\n\tif db != nil {\n\t\terr := db.Read(func(rx *gorm.DB) error {\n\t\t\treturn rx.Model(&types.Node{}).Pluck(\"ipv4\", &v4s).Error\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"reading IPv4 addresses from database: %w\", err)\n\t\t}\n\n\t\terr = db.Read(func(rx *gorm.DB) error {\n\t\t\treturn rx.Model(&types.Node{}).Pluck(\"ipv6\", &v6s).Error\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"reading IPv6 addresses from database: %w\", err)\n\t\t}\n\t}\n\n\tvar ips netipx.IPSetBuilder\n\n\t// Add network and broadcast addrs to used pool so they\n\t// are not handed out to nodes.\n\tif prefix4 != nil {\n\t\tnetwork4, broadcast4 := util.GetIPPrefixEndpoints(*prefix4)\n\t\tips.Add(network4)\n\t\tips.Add(broadcast4)\n\n\t\t// Use network as starting point, it will be used to call .Next()\n\t\t// TODO(kradalby): Could potentially take all the IPs loaded from\n\t\t// the database into account to start at a more \"educated\" location.\n\t\tret.prev4 = network4\n\t}\n","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/ip.go#L68-L104","documentation":"Companion to error 411: the allocator plucks the ipv6 column of all nodes to seed the used-IP set. Fails for the same classes of reason — broken connection, missing ipv6 column, or a blocked read — just on the second query.","triggerScenarios":"Identical to 411 but affecting the ipv6 pluck: schema without nodes.ipv6, connection drop between the two queries, sqlite busy timeout.","commonSituations":"Same as 411; seeing 412 but not 411 suggests a transient failure between the two reads rather than schema drift.","solutions":["Same remediation as 411: align schema and binary versions, verify connectivity.","If 411 succeeded but 412 failed, suspect a transient — one restart usually resolves it.","Confirm prefixes (ip_prefixes) config includes valid v4/v6 entries so the allocator is built against a sane config."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Mirror of 411: catch, inspect chained driver error, distinguish\n// 'column doesn't exist' (run migrations) from transient connection loss\n// (retry once after re-dial).","preventionTips":["Ensure schema and binary versions match before startup.","Use connection pooling with health checks for postgres."],"tags":["database","ip-allocation","ipv6","startup"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}