juanfont/headscale · error · ErrPathIsDirectory
%w: %s
Error message
%w: %s
What it means
NewExtraRecordsManager refuses the configured extra-records path because os.Stat reports it is a directory, not a file. Extra records must be a JSON file of tailcfg.DNSRecord entries; the manager reads it and sets up an fsnotify watcher on a file, so a directory is a configuration error. The sentinel ErrPathIsDirectory is wrapped with the path.
Source
Thrown at hscontrol/dns/extrarecords.go:46
updateCh chan []tailcfg.DNSRecord
closeCh chan struct{}
hash [32]byte
}
// NewExtraRecordsManager creates a new [ExtraRecordsMan] and starts watching the file at the given path.
func NewExtraRecordsManager(path string) (*ExtraRecordsMan, error) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, fmt.Errorf("creating watcher: %w", err)
}
fi, err := os.Stat(path)
if err != nil {
return nil, fmt.Errorf("getting file info: %w", err)
}
if fi.IsDir() {
return nil, fmt.Errorf("%w: %s", ErrPathIsDirectory, path)
}
records, hash, err := readExtraRecordsFromPath(path)
if err != nil {
return nil, fmt.Errorf("reading extra records from path: %w", err)
}
er := &ExtraRecordsMan{
watcher: watcher,
path: path,
records: set.SetOf(records),
hash: hash,
closeCh: make(chan struct{}),
updateCh: make(chan []tailcfg.DNSRecord),
}
err = watcher.Add(path)
if err != nil {View on GitHub (pinned to 565fd254d0)
Solutions
- Point dns.extra_records_path at a JSON file, e.g. /etc/headscale/extra-records.json containing an array of records.
- Create the file with '[]' if you have no extra records yet.
- Remove any trailing slash or directory path from the setting.
Example fix
// config before dns: extra_records_path: /etc/headscale/records/ # directory -> error // config after dns: extra_records_path: /etc/headscale/records.json # file with []tailcfg.DNSRecord
Defensive patterns
Strategy: validation
Validate before calling
fi, err := os.Stat(path)
if err != nil {
return err
}
if fi.IsDir() {
return fmt.Errorf("extra records path %q must be a file, not a directory", path)
} Prevention
- Point dns.extra_records_path at a concrete .json filename.
- Create the file ('[]' if empty) before first startup.
- Lint configs to reject directory paths or trailing slashes for file settings.
When it happens
Trigger: Setting dns.extra_records_path in headscale's config to a directory (e.g. '/etc/headscale/' instead of '/etc/headscale/extra-records.json'), or to a path whose parent was created but the file itself was never added.
Common situations: Config typo with a trailing slash or missing filename; users pointing at a directory of record files expecting them to be merged; the file not yet created at first startup.
Related errors
- unmarshalling records, content: %q: %w
- unmarshalling dns extra records: %w
- path cannot be empty
- busy_timeout must be >= 0
- wildcard (*) is not supported as SSH destination
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/eeb23eca5a6c096a.
Report an issue: GitHub.