netbirdio/netbird · warning
unhandled protocol type: %d
Error message
unhandled protocol type: %d
What it means
The default arm of the switch in toExposeProtocol: ParseProtocolType returned a ProtocolType value that has no mapping to a proto.ExposeProtocol enum. Today ParseProtocolType can only return the values 0-4 and every one has a case, so this branch is dead code — a guard ensuring that any protocol constant added to client/internal/expose/protocol.go without a matching mapping here fails loudly instead of silently mapping to zero (EXPOSE_HTTP).
Source
Thrown at client/cmd/expose.go:233
func toExposeProtocol(exposeProtocol string) (proto.ExposeProtocol, error) {
p, err := expose.ParseProtocolType(exposeProtocol)
if err != nil {
return 0, fmt.Errorf("invalid protocol: %w", err)
}
switch p {
case expose.ProtocolHTTP:
return proto.ExposeProtocol_EXPOSE_HTTP, nil
case expose.ProtocolHTTPS:
return proto.ExposeProtocol_EXPOSE_HTTPS, nil
case expose.ProtocolTCP:
return proto.ExposeProtocol_EXPOSE_TCP, nil
case expose.ProtocolUDP:
return proto.ExposeProtocol_EXPOSE_UDP, nil
case expose.ProtocolTLS:
return proto.ExposeProtocol_EXPOSE_TLS, nil
default:
return 0, fmt.Errorf("unhandled protocol type: %d", p)
}
}
func handleExposeReady(cmd *cobra.Command, stream proto.DaemonService_ExposeServiceClient, port uint64) error {
event, err := stream.Recv()
if err != nil {
return fmt.Errorf("receive expose event: %v", status.Convert(err).Message())
}
ready, ok := event.Event.(*proto.ExposeServiceEvent_Ready)
if !ok {
return fmt.Errorf("unexpected expose event: %T", event.Event)
}
printExposeReady(cmd, ready.Ready, port)
return nil
}
func printExposeReady(cmd *cobra.Command, r *proto.ExposeServiceReady, port uint64) {View on GitHub (pinned to 93e97f4bf1)
Solutions
- As a user: not reachable in stock builds — if a custom build shows it, update/rebuild so both enums match
- As a contributor: in the same change that adds the expose.ProtocolType constant, add its case mapping to the matching proto.ExposeProtocol_* value in toExposeProtocol
Example fix
// before: new constant parsed but not mapped -> hits default
case expose.ProtocolTLS:
return proto.ExposeProtocol_EXPOSE_TLS, nil
default:
return 0, fmt.Errorf("unhandled protocol type: %d", p)
// after: add the mapping when introducing the constant
case expose.ProtocolTLS:
return proto.ExposeProtocol_EXPOSE_TLS, nil
case expose.ProtocolQUIC:
return proto.ExposeProtocol_EXPOSE_QUIC, nil
default:
return 0, fmt.Errorf("unhandled protocol type: %d", p) Defensive patterns
Strategy: type-guard
Type guard
func protocolTypeSupported(p expose.ProtocolType) bool {
switch p {
case expose.ProtocolHTTP, expose.ProtocolHTTPS, expose.ProtocolTCP, expose.ProtocolUDP, expose.ProtocolTLS:
return true
default:
return false
}
} Prevention
- When adding a ProtocolType constant, add its proto mapping case in the same commit and run the expose tests
- Treat this error text ('unhandled protocol type') as a build-time contract break, not a runtime condition to catch
- Add a unit test enumerating every ParseProtocolType-accepted string through toExposeProtocol so drift fails CI
When it happens
Trigger: A contributor adds a new constant to ProtocolType (e.g. ProtocolQUIC = 5) and extends ParseProtocolType to parse it, but forgets the corresponding case in toExposeProtocol's switch.
Common situations: Two-file feature changes where one hunk is lost in rebase; forks adding protocols to the expose package only; copy of the mapping drifting during refactors.
Related errors
- invalid protocol: %w
- unsupported protocol %q: must be http, https, tcp, udp, or t
- auth flags (--with-pin, --with-password, --with-user-groups)
- invalid message length
- invalid magic header
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/1de571afdae43d42.
Report an issue: GitHub.