netbirdio/netbird · info

peer registered again

Error message

peer registered again

What it means

Sentinel error returned by Server.ConnectStream's select loop when the stream's own context is canceled because a newer registration for the same peer ID replaced this peer in the registry (the winning Register call cancels the old peer's context). It simply terminates the superseded gRPC stream; the peer's newest connection is unaffected.

Source

Thrown at signal/server/signal.go:49

	labelTypeMessage       = "message"
	labelTypeTimeout       = "timeout"
	labelTypeDisconnected  = "disconnected"

	labelError                   = "error"
	labelErrorMissingId          = "missing_id"
	labelErrorMissingMeta        = "missing_meta"
	labelErrorFailedHeader       = "failed_header"
	labelErrorFailedRegistration = "failed_registration"

	labelRegistrationStatus   = "status"
	labelRegistrationFound    = "found"
	labelRegistrationNotFound = "not_found"

	sendTimeout = 10 * time.Second
)

var (
	ErrPeerRegisteredAgain = errors.New("peer registered again")
)

// Server an instance of a Signal server
type Server struct {
	registry *peer.Registry
	proto.UnimplementedSignalExchangeServer
	dispatcher *dispatcher.Dispatcher
	metrics    *metrics.AppMetrics

	successHeader metadata.MD

	sendTimeout time.Duration
}

// NewServer creates a new Signal server
func NewServer(ctx context.Context, meter metric.Meter, metricsPrefix ...string) (*Server, error) {
	appMetrics, err := metrics.NewAppMetrics(meter, metricsPrefix...)
	if err != nil {

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. No server fix needed: this is the designed stream-replacement path, not a fault.
  2. On the client, confirm registration via the success header and reconnect on stream end.
  3. Eliminate duplicate agents sharing one key to avoid constant stream stealing.
Defensive patterns

Strategy: try-catch

Type guard

func isPeerRegisteredAgain(err error) bool {
    return errors.Is(err, signal.ErrPeerRegisteredAgain)
}

Try / catch

err := streamHandler(stream) // ConnectStream
if err != nil && errors.Is(err, signal.ErrPeerRegisteredAgain) {
    // this stream was superseded by a newer one for the same peer:
    // expected during reconnect, exit the handler quietly
    return nil
}

Prevention

When it happens

Trigger: A peer opens a second ConnectStream while the first is still live; the newer stream's registration wins, and the older stream exits via ctx.Done() with this error.

Common situations: Agent reconnecting without waiting for the old stream teardown; duplicate client instances using the same key; expected noise during rapid reconnects.

Related errors


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