{"record":{"id":"f046c33da14779e6","repo":"netbirdio/netbird","slug":"device-is-not-ready-yet-f046c3","errorCode":null,"errorMessage":"device is not ready yet","messagePattern":"device is not ready yet","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/iface/device/device_netstack.go","lineNumber":102,"sourceCode":"\t\tdevice.NewLogger(wgLogLevel(), \"[netbird] \"),\n\t)\n\n\tt.configurer = configurer.NewUSPConfigurerNoUAPI(t.device, t.name, t.bind.ActivityRecorder())\n\terr = t.configurer.ConfigureInterface(t.key, t.port)\n\tif err != nil {\n\t\tif cErr := tunIface.Close(); cErr != nil {\n\t\t\tlog.Debugf(\"failed to close tun device: %v\", cErr)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"error configuring interface: %s\", err)\n\t}\n\n\tlog.Debugf(\"device has been created: %s\", t.name)\n\treturn t.configurer, nil\n}\n\nfunc (t *TunNetstackDevice) Up() (*udpmux.UniversalUDPMuxDefault, error) {\n\tif t.device == nil {\n\t\treturn nil, fmt.Errorf(\"device is not ready yet\")\n\t}\n\n\terr := t.device.Up()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tudpMux, err := t.bind.GetICEMux()\n\tif err != nil && !errors.Is(err, bind.ErrUDPMUXNotSupported) {\n\t\treturn nil, err\n\t}\n\n\tif udpMux != nil {\n\t\tt.udpMux = udpMux\n\t}\n\n\tlog.Debugf(\"netstack device is ready to use\")\n\treturn udpMux, nil","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/iface/device/device_netstack.go#L84-L120","documentation":"Returned by TunNetstackDevice.Up() when t.device is still nil. t.device is only assigned inside create(), so this error means Up() ran before a successful create(), or the create() error was swallowed and the caller continued anyway. It is a lifecycle-ordering error, not an environment failure.","triggerScenarios":"Calling Up() before Create(); discarding the error returned by Create() and proceeding; a reconnect/retry path that skips the create step; Close() nil racing a new Up().","commonSituations":"Custom embedding of the agent that drives Up/Close directly, goroutine races between teardown and reconnect, tests invoking Up() on a fresh device.","solutions":["Call Create() first and handle its error before ever calling Up()","Serialize Create/Up/Close behind a mutex or state machine so teardown cannot interleave with bring-up","On this error, re-run Create() and then retry Up()"],"exampleFix":"// before\nmux, err := dev.Up()\n\n// after\nif dev.Device() == nil {\n    if _, cerr := dev.Create(); cerr != nil {\n        return nil, cerr\n    }\n}\nmux, err := dev.Up()","handlingStrategy":"validation","validationCode":"// Up() requires a successfully created device; check readiness first\nif dev.Device() == nil {\n    if _, err := dev.Create(); err != nil {\n        return nil, err\n    }\n}","typeGuard":"func netstackReady(dev *device.TunNetstackDevice) bool {\n    return dev != nil && dev.Device() != nil\n}","tryCatchPattern":"mux, err := dev.Up()\nif err != nil && strings.Contains(err.Error(), \"device is not ready yet\") {\n    // re-create then retry Up once\n}","preventionTips":["Treat Create/Up/Close as one state machine owned by a single goroutine","Always check the error returned by Create() before any subsequent call","In reconnect loops, restart from Create(), not from Up()"],"tags":["netstack","lifecycle","state","go"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}