netbirdio/netbird · error

unexpected expose event: %T

Error message

unexpected expose event: %T

What it means

The first event received on the ExposeService stream was not the expected *proto.ExposeServiceEvent_Ready wrapper. The code uses a comma-ok type assertion on the event's oneof field and prints the actual Go type with %T. This guards the CLI/daemon event contract: the daemon is expected to send Ready first, and any other leading event means the two sides disagree on the protocol.

Source

Thrown at client/cmd/expose.go:245

		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) {
	cmd.Println("Service exposed successfully!")
	cmd.Printf("  Name:     %s\n", r.ServiceName)
	if r.ServiceUrl != "" {
		cmd.Printf("  URL:      %s\n", r.ServiceUrl)
	}
	if r.Domain != "" && !isPortBasedProtocol(exposeProtocol) {
		cmd.Printf("  Domain:   %s\n", r.Domain)
	}
	cmd.Printf("  Protocol: %s\n", exposeProtocol)
	cmd.Printf("  Internal: %d\n", port)
	if isClusterProtocol(exposeProtocol) {
		cmd.Printf("  External: %s\n", extractPort(r.ServiceUrl, resolveExternalPort(port)))

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Align the CLI and daemon on the same release: check `netbird version` and the daemon version in `netbird status`, and upgrade whichever is older
  2. Restart the daemon after upgrading so both sides speak the same event contract, then re-run
  3. If it persists on matched versions, report the event type shown in the message — it identifies which side introduced the divergence
Defensive patterns

Strategy: type-guard

Type guard

func isReadyEvent(e *proto.ExposeServiceEvent) (*proto.ExposeServiceReady, bool) {
	if ready, ok := e.Event.(*proto.ExposeServiceEvent_Ready); ok {
		return ready.Ready, true
	}
	return nil, false
}

Prevention

When it happens

Trigger: A daemon from a different release emits a new event type (progress, log, or error event) before Ready; a fork extends proto.ExposeServiceEvent with extra oneof members and sends one first; a future protocol change reorders events.

Common situations: CLI and daemon binaries from different NetBird releases after a partial upgrade; custom builds where client/proto was regenerated on one side only.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/0e9f20e78286a51e. Report an issue: GitHub.