netbirdio/netbird · warning

peer already registered

Error message

peer already registered

What it means

Sentinel error from the signal server's peer registry. Registry.Register uses sync.Map LoadOrStore: if a Peer with the same ID already exists and the incoming stream's StreamID is NOT greater than the stored one, the registration is rejected with this error so a stale stream cannot evict a live one. A strictly newer StreamID instead replaces and cancels the old peer.

Source

Thrown at signal/peer/peer.go:17

package peer

import (
	"context"
	"sync"
	"time"

	"errors"

	log "github.com/sirupsen/logrus"

	"github.com/netbirdio/netbird/shared/signal/proto"
	"github.com/netbirdio/netbird/signal/metrics"
)

var (
	ErrPeerAlreadyRegistered = errors.New("peer already registered")
)

// Peer representation of a connected Peer
type Peer struct {
	// a unique id of the Peer (e.g. sha256 fingerprint of the Wireguard public key)
	Id string

	StreamID int64

	// a gRpc connection stream to the Peer
	Stream proto.SignalExchange_ConnectStreamServer
	// sendMu serializes writes to Stream. gRPC forbids concurrent SendMsg on
	// the same ServerStream, and a peer can be the target of many senders at
	// once.
	sendMu sync.Mutex

	// registration time
	RegisteredAt time.Time

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Let the previous stream close and deregistration finish before reconnecting.
  2. On the agent side, treat it as transient: the built-in reconnect loop eventually registers with a higher StreamID.
  3. Ensure only one agent process runs per WireGuard key.
Defensive patterns

Strategy: try-catch

Type guard

func isPeerAlreadyRegistered(err error) bool {
    return errors.Is(err, peer.ErrPeerAlreadyRegistered)
}

Try / catch

if err := registry.Register(p); err != nil {
    if errors.Is(err, peer.ErrPeerAlreadyRegistered) {
        // stale or duplicate stream: do not fail the server; the newer
        // registration wins and the client reconnect loop will retry
        log.Debugf("stale registration rejected: %v", err)
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: A peer reconnects to the signal server while its previous stream is still registered and the new stream carries an equal or older StreamID; two concurrent ConnectStream calls for the same peer fingerprint (sha256 of the WireGuard public key).

Common situations: Reconnect storms after network flaps where the old stream has not been deregistered yet; accidentally running two agents with the same key; test harnesses opening parallel streams with the same peer ID.

Related errors


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