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

  1. Ensure the value is a valid CIDR like '100.64.0.0/24' or single IP ('100.64.0.1' is normalized by parseString).
  2. Initialize all Prefix fields before calling Validate; never pass netip.Prefix{}.
  3. 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

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.