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 errView on GitHub (pinned to 565fd254d0)
Solutions
- Quote every destination: "80" or better "web:80", never bare 80.
- Fix converters to always emit strings for destination entries.
- 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
- Always quote destinations in HuJSON/JSON.
- Check converter output for bare scalars.
- Programmatic policies: only strings in destination fields.
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.