juanfont/headscale · error
%w: %s
Error message
%w: %s
What it means
Prefix.Validate rejected an IP prefix because netip.Prefix(*p).IsValid() is false — the zero Prefix (uninitialized) or an invalid bit combination. The value is printed via %s and typically shows as 'invalid IP' or the zero prefix.
Source
Thrown at hscontrol/policy/v2/types.go:652
errs = append(errs, err)
}
// Address-based aliases (host names) resolve to exactly the
// literal prefix from the hosts map. They do NOT expand to
// include the matching node's other IP addresses.
ips.AddPrefix(netip.Prefix(pref))
return buildIPSetMultiErr(&ips, errs)
}
type Prefix netip.Prefix
func (p *Prefix) Validate() error {
if netip.Prefix(*p).IsValid() {
return nil
}
return fmt.Errorf("%w: %s", ErrInvalidPrefix, p.String())
}
func (p *Prefix) String() string {
return netip.Prefix(*p).String()
}
func (p *Prefix) parseString(addr string) error {
if !strings.Contains(addr, "/") {
addr, err := netip.ParseAddr(addr)
if err != nil {
return err
}
addrPref, err := addr.Prefix(addr.BitLen())
if err != nil {
return err
}
View on GitHub (pinned to 565fd254d0)
Solutions
- Ensure the value is a valid CIDR like '100.64.0.0/24' or single IP ('100.64.0.1' is normalized by parseString).
- Initialize all Prefix fields before calling Validate; never pass netip.Prefix{}.
- Validate at parse time with parseString so bad input fails early with the original parse error.
Example fix
// before
p := policy.Prefix{}
err := p.Validate()
// after
var p policy.Prefix
err := p.parseString("100.64.0.0/24")
if err == nil { err = p.Validate() } Defensive patterns
Strategy: validation
Validate before calling
// Parse-and-validate in one step; never validate zero values.
var p v2.Prefix
if err := p.ParseString(cidrString); err != nil { return err }
if err := p.Validate(); err != nil { return err } Try / catch
if err := p.Validate(); err != nil {
if errors.Is(err, v2.ErrInvalidPrefix) {
// re-parse from the original string; zero-value prefix means upstream init bug
}
return err
} Prevention
- Never construct Prefix zero values; always parse from strings.
- Fail at parse time (parseString) so the original netip error is preserved.
- Unit-test policy generators to assert every emitted prefix validates.
When it happens
Trigger: A policy field expects a prefix but receives garbage that still unmarshalled (e.g. via parseString partial failure), or code constructs Prefix{} without initializing it before Validate. netip.Prefix.IsValid() is false.
Common situations: Programmatic policy generation leaving zero-value prefixes; JSON with an empty string that bypassed parse errors; copying prefixes between structs and dropping initialization.
Related errors
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/9f7f8bfe338b25c6.
Report an issue: GitHub.