juanfont/headscale · error

%w, got: %v(%d)

Error message

%w, got: %v(%d)

What it means

ProtocolPort.UnmarshalJSON split a protocol:port string on ':' and got a part count other than 2 — too many colons or none where the string still reached this branch. ErrProtocolPortInvalidFormat reports the parts and their count so the malformed shape is obvious.

Source

Thrown at hscontrol/policy/v2/types.go:957

			return nil
		}

		// Only contains a port, no protocol
		if !strings.Contains(vs, ":") {
			ports, err := parsePortRange(vs)
			if err != nil {
				return fmt.Errorf("port range %q: %w", vs, err)
			}

			ve.Protocol = ProtocolNameWildcard
			ve.Ports = ports

			return nil
		}

		parts := strings.Split(vs, ":")
		if len(parts) != 2 {
			return fmt.Errorf("%w, got: %v(%d)", ErrProtocolPortInvalidFormat, parts, len(parts))
		}

		protocol := Protocol(parts[0])

		err := protocol.validate()
		if err != nil {
			return err
		}

		portsPart := parts[1]

		ports, err := parsePortRange(portsPart)
		if err != nil {
			return fmt.Errorf("port range %q: %w", portsPart, err)
		}

		ve.Protocol = protocol
		ve.Ports = ports

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Use exactly one colon: 'tcp:80' or a range 'tcp:80-90'.
  2. List additional ports as separate entries in the protocols array.
  3. Validate generated policy strings contain at most one colon before writing them.

Example fix

// before
"protocols": ["tcp:80:443"]

// after
"protocols": ["tcp:80", "tcp:443"]
Defensive patterns

Strategy: validation

Validate before calling

if strings.Count(s, ":") != 1 {
    return fmt.Errorf("%q must be exactly 'proto:port' (one colon)", s)
}

Try / catch

if err := pp.UnmarshalJSON([]byte(s)); err != nil {
    if errors.Is(err, v2.ErrProtocolPortInvalidFormat) {
        // message prints parts and count; split into multiple entries
    }
    return err
}

Prevention

When it happens

Trigger: Values like 'tcp:80:443' (two colons, three parts) or an empty/edge string that splits oddly. len(parts) != 2 after strings.Split(vs, ":").

Common situations: Trying to list multiple ports with colons ('tcp:80:443') instead of a range; IPv6 literals or URLs accidentally placed in the protocols field; double separators from concatenation bugs.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/b3627da95de05065. Report an issue: GitHub.