juanfont/headscale · error

%w: %T

Error message

%w: %T

What it means

The destination unmarshaller received a JSON value of an unsupported type (anything other than string — number, bool, object, array). Go's type switch has no case for it, so it fails with ErrTypeNotSupported and the concrete Go type name (%T).

Source

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

				)
			}

			ve.Ports = ports
		} else {
			return ErrHostportMissingColon
		}

		ve.Alias, err = parseAlias(vs)
		if err != nil {
			return err
		}

		if err := ve.Validate(); err != nil { //nolint:noinlineerr
			return err
		}

	default:
		return fmt.Errorf("%w: %T", ErrTypeNotSupported, vs)
	}

	return nil
}

// ProtocolPort is a representation of the "network layer capabilities"
// of a Grant.
type ProtocolPort struct {
	Ports    []tailcfg.PortRange
	Protocol Protocol
}

func (ve *ProtocolPort) UnmarshalJSON(b []byte) error {
	var v any

	err := json.Unmarshal(b, &v)
	if err != nil {
		return err

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Quote every destination: "80" or better "web:80", never bare 80.
  2. Fix converters to always emit strings for destination entries.
  3. When building policies programmatically, use string values only.

Example fix

// before
"accept": [80]

// after
"accept": ["web:80"]
Defensive patterns

Strategy: type-guard

Validate before calling

// Ensure the JSON value is a string before unmarshalling.
if raw[0] != '"' {
    return fmt.Errorf("destination must be a JSON string, got: %s", raw)
}

Type guard

func isDestinationString(v any) bool {
    _, ok := v.(string)
    return ok
}

Try / catch

if err := json.Unmarshal(data, &dst); err != nil {
    if errors.Is(err, v2.ErrTypeNotSupported) {
        // %T in the message reveals the offending Go type; quote the value
    }
    return err
}

Prevention

When it happens

Trigger: A test destination written as a JSON number ({"accept": [80]}), a boolean, or a nested object. The type switch in the destination unmarshaller falls through to default.

Common situations: Quotes lost when editing HuJSON (bare numeric destination); YAML-to-HuJSON conversion emitting non-string scalars; hand-built map[string]interface{} policies passed to the unmarshaller.

Related errors


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