{"record":{"id":"0228654de4ef26fe","repo":"netbirdio/netbird","slug":"engine-is-not-initialized-022865","errorCode":null,"errorMessage":"engine is not initialized","messagePattern":"engine is not initialized","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/android/session.go","lineNumber":287,"sourceCode":"}\n\nfunc (c *Client) endExtend() {\n\tc.extendMu.Lock()\n\tdefer c.extendMu.Unlock()\n\tif c.extendCancel != nil {\n\t\tc.extendCancel()\n\t\tc.extendCancel = nil\n\t}\n}\n\nfunc (c *Client) extendAuthSession(ctx context.Context, urlOpener URLOpener, isAndroidTV bool) error {\n\tcfg, cfgPath, cc := c.authSnapshot()\n\tif cfg == nil || cc == nil {\n\t\treturn fmt.Errorf(\"engine is not running\")\n\t}\n\tengine := cc.Engine()\n\tif engine == nil {\n\t\treturn fmt.Errorf(\"engine is not initialized\")\n\t}\n\n\tauthClient, err := auth.NewAuth(ctx, cfg.PrivateKey, cfg.ManagementURL, cfg)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create auth client: %v\", err)\n\t}\n\tdefer authClient.Close()\n\n\t// Passing the config path makes the flow pick up the login_hint: an extend\n\t// renews the session of the account already signed in, so it must not stop to\n\t// offer a choice.\n\ta := NewAuthWithConfig(ctx, cfg, cfgPath)\n\ttokenInfo, err := a.foregroundGetTokenInfo(authClient, urlOpener, isAndroidTV)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"interactive sso login failed: %v\", err)\n\t}\n\n\tif _, err := engine.ExtendAuthSession(ctx, tokenInfo.GetTokenToUse()); err != nil {","sourceCodeStart":269,"sourceCodeEnd":305,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/android/session.go#L269-L305","documentation":"Returned by extendAuthSession in the Android client when the profile config and gRPC connection snapshot exist but the connection's Engine() is nil. The engine is the component that owns the WireGuard interface and the management session, so a nil engine means the client object exists but its network engine was never started or has already been stopped. Without it there is no session to extend and no way to forward the renewed token.","triggerScenarios":"Calling the Android SessionExtend/mobile binding right after startup before the connect flow finished creating the engine; calling extend after the client was disconnected (engine torn down) while a stale client connection object is still returned by authSnapshot; race between a stop/login-required transition and the extend call.","commonSituations":"Android app calls extend immediately after process restore (background restore, app swipe-away and relaunch) before netbird up/connect completes; extending a session after logout or after a crash-recovery path that nils the engine; TV flows where the UI button is enabled before the service reports connected.","solutions":["Wait for the connected/running state (e.g. the connect callback or OnClientConnected) before invoking the extend API","If it persists after connect completes, restart the client session: call down/up (or Service_Stop then Service_Start) so a fresh engine is created","Check that only one extend is in flight — beginExtend rejects concurrent extends with a different error, but a stuck extend can leave the client in a bad state; call endExtend/cancel path","If reproducible, capture logs for whether Stop() ran between connect and extend (engine set to nil on teardown)"],"exampleFix":"// before: extend invoked as soon as the screen showing the expiry warning appears\nclient.extendAuthSession(ctx, opener, false)\n\n// after: only extend once the client reports a running engine\nif cc := client.conn(); cc == nil || cc.Engine() == nil {\n    // not connected yet; retry after the connect callback fires\n    return\n}\nclient.extendAuthSession(ctx, opener, false)","handlingStrategy":"type-guard","validationCode":"// Java/Android binding side: only offer the extend action when the engine is live\n// (the Go API surface: c.conn() != nil && c.conn().Engine() != nil)\n// Via the mobile SDK this maps to checking the connected state callback first.\nif (!isClientConnected()) { // set from the connect callback, cleared on disconnect\n    showRetryWhenConnected();\n    return;\n}\nnetbirdClient.extendAuthSession(opener, isTv);","typeGuard":"// Go, if calling extendAuthSession from other client code:\nfunc (c *Client) canExtend() bool {\n    cfg, _, cc := c.authSnapshot()\n    return cfg != nil && cc != nil && cc.Engine() != nil\n}","tryCatchPattern":null,"preventionTips":["Drive the extend UI from the connected/disconnected state callback rather than from the session-expiry warning alone","Treat 'engine is not initialized' and 'engine is not running' as distinct: the first means retry after connect, the second means the client is logged out","Never cache the Client/connection object across a stop; re-acquire it per extend attempt via authSnapshot semantics"],"tags":["android","session","engine","initialization","mobile"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}