{"record":{"id":"913ab6973c27e304","repo":"netbirdio/netbird","slug":"peer-registered-again","errorCode":null,"errorMessage":"peer registered again","messagePattern":"peer registered again","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"signal/server/signal.go","lineNumber":49,"sourceCode":"\tlabelTypeMessage       = \"message\"\n\tlabelTypeTimeout       = \"timeout\"\n\tlabelTypeDisconnected  = \"disconnected\"\n\n\tlabelError                   = \"error\"\n\tlabelErrorMissingId          = \"missing_id\"\n\tlabelErrorMissingMeta        = \"missing_meta\"\n\tlabelErrorFailedHeader       = \"failed_header\"\n\tlabelErrorFailedRegistration = \"failed_registration\"\n\n\tlabelRegistrationStatus   = \"status\"\n\tlabelRegistrationFound    = \"found\"\n\tlabelRegistrationNotFound = \"not_found\"\n\n\tsendTimeout = 10 * time.Second\n)\n\nvar (\n\tErrPeerRegisteredAgain = errors.New(\"peer registered again\")\n)\n\n// Server an instance of a Signal server\ntype Server struct {\n\tregistry *peer.Registry\n\tproto.UnimplementedSignalExchangeServer\n\tdispatcher *dispatcher.Dispatcher\n\tmetrics    *metrics.AppMetrics\n\n\tsuccessHeader metadata.MD\n\n\tsendTimeout time.Duration\n}\n\n// NewServer creates a new Signal server\nfunc NewServer(ctx context.Context, meter metric.Meter, metricsPrefix ...string) (*Server, error) {\n\tappMetrics, err := metrics.NewAppMetrics(meter, metricsPrefix...)\n\tif err != nil {","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/signal/server/signal.go#L31-L67","documentation":"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.","triggerScenarios":"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.","commonSituations":"Agent reconnecting without waiting for the old stream teardown; duplicate client instances using the same key; expected noise during rapid reconnects.","solutions":["No server fix needed: this is the designed stream-replacement path, not a fault.","On the client, confirm registration via the success header and reconnect on stream end.","Eliminate duplicate agents sharing one key to avoid constant stream stealing."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isPeerRegisteredAgain(err error) bool {\n    return errors.Is(err, signal.ErrPeerRegisteredAgain)\n}","tryCatchPattern":"err := streamHandler(stream) // ConnectStream\nif err != nil && errors.Is(err, signal.ErrPeerRegisteredAgain) {\n    // this stream was superseded by a newer one for the same peer:\n    // expected during reconnect, exit the handler quietly\n    return nil\n}","preventionTips":["Filter this sentinel from signal-server error dashboards; it is normal stream replacement.","Client-side, confirm registration with the success header rather than assuming the first stream stays alive.","Design clients to reconnect when a stream ends, since a newer stream can terminate an older one at any time."],"tags":["signal","grpc","stream-lifecycle","sentinel-error"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}