fix(controller): recover from interrupted cloud login browser flow#892
Merged
fix(controller): recover from interrupted cloud login browser flow#892
Conversation
When the user closes the authorization browser tab without completing device login, the controller's 5-minute poll kept running and every subsequent click on "Login with Nexu account" was rejected with "Connection attempt already in progress", leaving the UI stuck. Treat a re-click while a poll is in flight as an explicit retry: abort the in-flight poll, clear the persisted polling flag, and start a fresh device registration. Remove the matching dead-end branch in the welcome page so the UI no longer swallows the retry. Fixes #865
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2015f301cf
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
added 2 commits
April 8, 2026 11:46
The polling reset block in connectDesktopCloud() was missing \`userId: null\` while every other reset site (polling expired/timeout branches, disconnect, switch profile) clears it. Without this the persisted state could keep a stale userId after aborting an in-flight device login, leaving UI metadata out of sync with the rest of the cloud profile fields.
Deploying nexu-docs with
|
| Latest commit: |
96c8707
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://a9518680.nexu-docs.pages.dev |
| Branch Preview URL: | https://fix-issue-865-cloud-login-st.nexu-docs.pages.dev |
added 2 commits
April 8, 2026 11:54
Address codex review on PR #892: when a stale poll's success/expired/ maxAttempts branch is processing in parallel with a fresh connectDesktopCloud() call, the old loop's "this.pollingState = null + setDesktopCloudState(...)" can clobber the new attempt's pollingState and persisted credentials. Identify the active poll by AbortSignal identity. Each final-state write now no-ops if the loop has been aborted or replaced, so the new device flow keeps full ownership of the polling state.
The "detects SKILL.md removal" test was using vitest's default 5000ms
timeout while internally calling waitUntil() which itself polls for up
to 5000ms. On macos-14 CI runners fsevents occasionally takes >1s to
deliver the unlink event, which is enough to make waitUntil() bump
into the test-level timeout before it can complete a successful poll.
Mirror the sibling "detects new SKILL.md" test which already declares
{ timeout: 10000 } for the same reason. This eliminates the recurring
flake on the macos-14 desktop-ci shard without changing watcher
semantics.
mrcfps
approved these changes
Apr 8, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Re-clicking Login with Nexu account after closing the authorization browser tab now starts a fresh login flow instead of getting stuck for 5 minutes.
Why
Closes #865. When a user started the Nexu cloud-account login and closed the authorization browser tab without completing it, the controller kept polling
/api/auth/device-pollfor up to 5 minutes and every subsequent click on Login was rejected with"Connection attempt already in progress". The frontend also deliberately short-circuited on that error whenstatus.polling === true, so the UI sat frozen on the waiting spinner and users had a dead-end state with no recovery path.How
Two small, targeted changes:
Controller (
apps/controller/src/store/nexu-config-store.ts)abortDesktopCloudPolling()that abortspollingState.abortControllerand clears the reference.connectDesktopCloud()no longer errors when a poll is already in flight. Instead it calls the helper, clears persisted desktop cloud state, and falls through to a freshdevice-register+ new poll. The "Already connected" guard is preserved.setDesktopCloudProfiles,switchDesktopCloudProfile,disconnectDesktopCloud) now use the shared helper.pollDesktopCloudAuthorization()already handlessignal.abortedat every await point and returns silently, so aborting an in-flight poll is race-free.Web (
apps/web/src/pages/welcome.tsx)if (data?.error === "Connection attempt already in progress") { ... }dead-end block inhandleAccountLogin. Any residual occurrence now falls through to the existing genericdata?.errorrecovery branch that calls disconnect + reconnect.Deliberately out of scope: the 100-attempt × 3s polling budget, adding an explicit "Cancel" button, any cloud-server-side TTL changes.
Affected areas
Checklist
pnpm typecheckpasses (controller + web)pnpm lintpassespnpm testpasses — pre-existing failures onorigin/maininsessions-runtime.test.ts,openclaw-config-compiler.test.ts,openclaw-sync.test.ts,route-compat.test.ts, and one unrelatednexu-config-store.test.tscase (imports cloud profiles and switches active profile while clearing cloud auth). None of the failures touch the cloud-login flow — they reproduce on pristineorigin/mainwithout this patch.pnpm generate-typesrun (if API routes/schemas changed) — n/a, no API routes or schemas changedanytypes introducedNotes for reviewers
Manual repro to verify the fix:
pnpm dev startdevice_id, desktop UI re-enters waiting state immediately, no error toast./workspace.pnpm dev logs controllerto confirm twodevice-registerPOSTs and that the first poll loop exited via the aborted signal without writing state afterward.Please also double-check: mid-poll Disconnect still works (now uses the shared
abortDesktopCloudPollinghelper instead of inline abort).