juanfont/headscale · error

, got: ( )

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.

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.

Appendix: 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)