{"record":{"id":"f3bc2f719279532d","repo":"juanfont/headscale","slug":"reading-path-s-err-w","errorCode":null,"errorMessage":"reading path: %s, err: %w","messagePattern":"reading path: (.+?), err: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/dns/extrarecords.go","lineNumber":193,"sourceCode":"\t// Release the lock before the (potentially blocking) send so a slow or\n\t// absent consumer cannot stall Records() readers, and abort the send on\n\t// shutdown instead of leaking this goroutine on the closed-down channel.\n\te.mu.Unlock()\n\n\tselect {\n\tcase e.updateCh <- toSend:\n\tcase <-e.closeCh:\n\t}\n}\n\n// readExtraRecordsFromPath reads a JSON file of [tailcfg.DNSRecord]\n// and returns the records and the hash of the file.\nfunc readExtraRecordsFromPath(path string) ([]tailcfg.DNSRecord, [32]byte, error) {\n\tvar zero [32]byte\n\n\tb, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn nil, zero, fmt.Errorf(\"reading path: %s, err: %w\", path, err)\n\t}\n\n\t// If the read was triggered too fast, and the file is not complete, ignore the update\n\t// if the file is empty. A consecutive update will be triggered when the file is complete.\n\tif len(b) == 0 {\n\t\treturn nil, zero, nil\n\t}\n\n\tvar records []tailcfg.DNSRecord\n\n\terr = json.Unmarshal(b, &records)\n\tif err != nil {\n\t\treturn nil, zero, fmt.Errorf(\"unmarshalling records, content: %q: %w\", string(b), err)\n\t}\n\n\thash := sha256.Sum256(b)\n\n\treturn records, hash, nil","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/dns/extrarecords.go#L175-L211","documentation":"os.ReadFile failed while loading (or hot-reloading) the extra-records JSON file; the path and the underlying OS error are both included. This fires from both NewExtraRecordsManager (startup) and the fsnotify reload loop (runtime), since both call readExtraRecordsFromPath.","triggerScenarios":"The file was deleted or renamed between the fsnotify event and the read (classic atomic-rename race: editors write a temp file and rename over the target), permissions lost, or the path became unreadable at runtime. Note: an empty file is tolerated (treated as no update), so this error means the read itself failed, not emptiness.","commonSituations":"Config management (ansible/sed -i) replacing the file via rename while headscale watches it; file removed by cleanup jobs; permission changes; NFS mounts dropping.","solutions":["Check the OS error in the chain (ENOENT, EACCES) — ENOENT during reload usually means the writer uses rename; ensure the final path exists after each write.","Restore correct ownership/permissions so the headscale process can read the file.","At startup, verify the path exists and is a regular file before starting headscale.","For reload failures, the manager keeps the last good records; fix the file and touch it to trigger another reload."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Before starting headscale:\nfi, err := os.Stat(cfg.ExtraRecordsPath)\nif err != nil || !fi.Mode().IsRegular() {\n    return fmt.Errorf(\"extra records file missing or not regular: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"records, hash, err := readExtraRecordsFromPath(path)\nif err != nil {\n    if errors.Is(err, fs.ErrNotExist) {\n        // transient during atomic-rename reloads: keep previous records,\n        // wait for the next fsnotify event instead of failing\n        return\n    }\n    return err\n}","preventionTips":["Write the file atomically (temp file + rename) so reloads never see a missing path.","Ensure the headscale process has read permission on the file and its directory.","Keep the previous records on reload failure — the manager does this; don't clear state on a transient read error."],"tags":["dns","filesystem","config-reload","fsnotify"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}