{"record":{"id":"cb69100e96956874","repo":"netbirdio/netbird","slug":"device-is-not-ready-yet-cb6910","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_usp_unix.go","lineNumber":80,"sourceCode":"\terr = t.assignAddr()\n\tif err != nil {\n\t\tt.device.Close()\n\t\treturn nil, fmt.Errorf(\"error assigning ip: %s\", err)\n\t}\n\n\tt.configurer = configurer.NewUSPConfigurer(t.device, t.name, t.iceBind.ActivityRecorder())\n\terr = t.configurer.ConfigureInterface(t.key, t.port)\n\tif err != nil {\n\t\tt.device.Close()\n\t\tt.configurer.Close()\n\t\treturn nil, fmt.Errorf(\"error configuring interface: %s\", err)\n\t}\n\treturn t.configurer, nil\n}\n\nfunc (t *TunDevice) 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.iceBind.GetICEMux()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tt.udpMux = udpMux\n\n\tlog.Debugf(\"device is ready to use: %s\", t.name)\n\treturn udpMux, nil\n}\n\nfunc (t *TunDevice) UpdateAddr(address wgaddr.Address) error {","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/iface/device/device_usp_unix.go#L62-L98","documentation":"Returned by TunDevice.Up() (userspace unix) when t.device is nil. t.device is only set inside Create() after the tun device is opened, so this error indicates Up() was called before a successful Create(), or a Create() error was ignored. Same lifecycle contract as the netstack variant.","triggerScenarios":"Calling Up() before Create(); ignoring the error from Create() and continuing; Close() and Up() racing from different goroutines.","commonSituations":"Reconnect logic that retries Up() without re-running Create(); concurrent engine stop/start; tests calling Up() directly.","solutions":["Guarantee Create() succeeded (error checked) before calling Up()","On reconnect paths, treat this error as a signal to re-run Create() first","Protect device lifecycle transitions with a mutex or single state owner"],"exampleFix":"// before\nmux, err := dev.Up()\n\n// after\nif _, cerr := dev.Create(); cerr != nil {\n    return nil, cerr\n}\nmux, err := dev.Up()","handlingStrategy":"validation","validationCode":"// Up() requires a device created by a successful Create()\nif dev.Device() == nil {\n    if _, err := dev.Create(); err != nil {\n        return nil, err\n    }\n}","typeGuard":"func deviceReady(dev *device.TunDevice) 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    // ordering bug: create first, then retry Up()\n}","preventionTips":["Check Create()'s error before every follow-up call","Drive reconnect flows from the top of the lifecycle (Create, then Up)","Serialize lifecycle transitions across goroutines"],"tags":["lifecycle","state","userspace","go"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}