{"record":{"id":"6f6d436ebb6e7ae4","repo":"netbirdio/netbird","slug":"waiting-for-browser-login-failed-v","errorCode":null,"errorMessage":"waiting for browser login failed: %v","messagePattern":"waiting for browser login failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/android/login.go","lineNumber":227,"sourceCode":"\t// profiles, not by logging out — logout keeps the email.\n\tif a.cfgPath != \"\" {\n\t\tif hint := readProfileEmail(a.cfgPath); hint != \"\" {\n\t\t\tif setter, ok := oAuthFlow.(loginHintSetter); ok {\n\t\t\t\tsetter.SetLoginHint(hint)\n\t\t\t}\n\t\t}\n\t}\n\n\tflowInfo, err := oAuthFlow.RequestAuthInfo(context.TODO())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting a request OAuth flow info failed: %v\", err)\n\t}\n\n\tgo urlOpener.Open(flowInfo.VerificationURIComplete, flowInfo.UserCode)\n\n\ttokenInfo, err := oAuthFlow.WaitToken(a.ctx, flowInfo)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"waiting for browser login failed: %v\", err)\n\t}\n\n\treturn &tokenInfo, nil\n}\n","sourceCodeStart":209,"sourceCodeEnd":232,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/android/login.go#L209-L232","documentation":"WaitToken blocks until the user completes authorization in the browser (device flow polls the IdP token endpoint; PKCE waits on a loopback callback). This error means the wait ended badly: the user code or PKCE grant expired, the user denied consent, the token endpoint kept erroring, or a.ctx was cancelled (caller stopping the flow). The browser URL was already handed to urlOpener.Open before the wait began.","triggerScenarios":"oAuthFlow.WaitToken(a.ctx, flowInfo) errors when the user never finishes or cancels the browser step before the code expires, the IdP returns access_denied or repeated polling errors, the network drops mid-poll, or a.ctx is cancelled while the login is in flight.","commonSituations":"User backgrounds the app and Android suspends the process, URLOpener.Open fails to launch the browser, user takes too long on the IdP login page, the app stops the client mid-login.","solutions":["Make sure urlOpener.Open actually launches the browser with flowInfo.VerificationURIComplete","Keep the app process alive while WaitToken runs (foreground service on Android)","If the code expired, simply retry Login — each attempt requests a new code","Do not cancel a.ctx while a login is in flight"],"exampleFix":"// before: any wait failure is fatal to the UI\nfunc (l *listener) OnError(err error) { l.showFatal(err) }\n\n// after: classify — expired/denied codes are recoverable by restarting the flow\nfunc (l *listener) OnError(err error) {\n\tmsg := err.Error()\n\tif strings.Contains(msg, \"waiting for browser login failed\") {\n\t\tl.promptRetry() // re-running Login gets a fresh user code\n\t\treturn\n\t}\n\tl.showFatal(err)\n}","handlingStrategy":"retry","validationCode":"// Before Login, verify the URLOpener will really open the verification URI;\n// a no-op Open guarantees the code expires and WaitToken fails.\nurlOpener.Open(uri, code) // must launch a browser / custom tab in production builds","typeGuard":null,"tryCatchPattern":"// Classify the wait failure: expired code or transient polling error → restart Login; explicit denial → stop\nif strings.Contains(err.Error(), \"waiting for browser login failed\") {\n\tif isAccessDenied(err) { showDenied() } else { promptRelogin() } // re-running Login gets a fresh code\n}","preventionTips":["Launch the browser immediately with VerificationURIComplete","Run the login inside a foreground service so Android does not suspend the wait","Never cancel the Auth context while a login is in flight","Show the user code in-app in case the URI opens the wrong page"],"tags":["android","go","oauth","browser-flow","timeout"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}