netbirdio/netbird · error

receive expose event: %v

Error message

receive expose event: %v

What it means

The first stream.Recv() on the server-streamed ExposeService call returned a gRPC error instead of the Ready event. Unlike the initial-RPC failure, the call was accepted and the stream opened — this error means the daemon failed (or the transport broke) while setting the exposure up and sending its first event. The gRPC status message is surfaced.

Source

Thrown at client/cmd/expose.go:240

	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) {
	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)

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Check the daemon log at the failure timestamp for the server-side reason (it usually knows more than the status message)
  2. Re-run the command — transient allocation and stream errors typically clear on retry
  3. If it fails consistently, verify daemon-to-management connectivity and that the account's services feature works (e.g. `netbird status` healthy, dashboard shows services)
  4. Avoid sending SIGINT before the Ready event is printed; wait for the success line before interrupting
Defensive patterns

Strategy: try-catch

Try / catch

event, err := stream.Recv()
if err != nil {
	if ctx.Err() != nil || status.Code(err) == codes.Canceled {
		return fmt.Errorf("expose interrupted before ready: %w", err)
	}
	// daemon-side setup failure before the Ready event; its log has the reason
	return fmt.Errorf("receive expose event: %s", status.Convert(err).Message())
}

Prevention

When it happens

Trigger: The daemon encounters a setup failure after accepting the RPC (proxy-cluster allocation error, management rejection) and sends an error status before Ready; the daemon process dies mid-setup; the context is canceled — exposeFn cancels ctx on SIGINT/SIGTERM, making Recv return context.Canceled; an IPC/socket failure on the open stream.

Common situations: Pressing Ctrl+C while waiting for the 'Service exposed successfully!' line; transient cluster allocation failures; daemon crash or restart during exposure setup; flaky daemon-to-management connectivity on the server side.

Related errors


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